Repository navigation
Releases: panaversity/ksor
@panaversity/ksor@0.0.61
b50f289 Patch Changes
-
c8df2a0:
pnpm devnow drops a document that is deleted or moved while it runs (#274).The dev server keeps a staged copy of the record, and its watcher carried edits
and new documents into that copy but never removals. A document deleted during
a review went on answering 200 at its old url and stayed in the sidebar, and a
moved one was listed twice, once at each path, until the dev server restarted.
Nothing told the owner to restart.The refresh now removes every staged file the record no longer holds, and every
folder that leaves empty, so the deleted or moved document answers 404 at its
old url and leaves the sidebar within about a second. A document whose audience
is edited so that the dev viewer may no longer read it leaves the same way.Removals were held back because a 2026-08-18 measurement found that deleting a
staged file took the dev server down. On fumadocs-mdx 15.4.0 and Next 16.3.3
it recovers on its own, but the race behind it remains: Turbopack can compile
before fumadocs has regenerated the collection, so the dev log may show
Module not foundfor the removed file, and a request in that moment can
answer with a 500. Measured on a fresh scaffold, another page polled every 20ms
did so one to four times in 6 of 8 removals, then answered 200 again. Builds
are unaffected: they never run the watcher.An existing project takes the fix with
ksor migrate --write-site, which
reissuessystem/site/lib/stage-knowledge.tswith the rest of the site. -
7d2e28a: A new npm or bun scaffold builds its site again.
mdast-util-to-markdown@2.1.3, published 2026-09-27, broke the MDX stringifier
offumadocs-core@16.15.4, the version the scaffold pins. Any page with bold or
italic text sent it into recursion without end, sonpm run buildand
bun run buildfailed withRangeError: Maximum call stack size exceeded
(fuma-nama/fumadocs#3604). The npm and bun scaffolds ship no lockfile, so a
fresh install picked up 2.1.3. Their rootpackage.jsonnow has
"overrides": { "mdast-util-to-markdown": "2.1.2" }, and their README explains
it. The pnpm scaffold has not changed, because its committed lockfile already
holds 2.1.2.If you scaffolded with npm or bun and your build now fails this way, add the
sameoverridesentry to your rootpackage.jsonand install again. Remove the
entry when you movefumadocs-coreto 16.15.15 or later, which has the
upstream fix.
Assets 2
@panaversity/ksor@0.0.60
cca9df8 Patch Changes
-
2a7ef92: Five things a live walk of the published 0.0.59 found, fixed.
verify.mjsreported ordinary markdown as an invented name. Its name regex
let\s+cross a blank line, so a## Mealsheading followed by a paragraph
openingOn travel…was extracted as the name "Meals On" and reported as
changed-or-introduced. That fires on the first document an agent converts —
the check meant to make conversion trustworthy was crying wolf. Names are now
capitalised words on ONE line; a name the source never mentions is still caught.A scaffold followed verbatim published its first generation untraceably.
ksor initrunsgit initand leaves zero commits, and its own epilogue went
install → dev → provision → refresh with no commit in between — so every
adopter's first publish saidsource: unspecifiedand skipped the R23
change-control check, which had no history to compare against. The epilogue now
says to commit before publishing, which is the whole fix.A plain build left a stale bundle tree unmentioned.
ksor buildrecomputes
everybundles[].sha256in the lock but only--bundleswrites the directory,
so after one--bundlesrun and any ordinary build the lock claimed a digest
nothing on disk produced, while the tree that exists to be SENT somewhere aged
silently. A plain build now says which build the directory came from and how to
refresh it. Reported, never deleted: it is the adopter's output and may be
mid-handover.Tutorial 01's first build block was one line short. R23 landed the day after
that walk, so the shipped block omittedchange-control: not checkedon a page
whose headline claim is that every output was pasted as it appeared. Re-captured
on 0.0.59, with a paragraph on what both honesty lines mean and when they go.The dev server's
/llms.txtdoes not change after an approval. Not a bug and
not fixable in the route:output: "export"requires a static route handler, so
pnpm devcomputes it once per process while the document's page beside it
updates. Stated in the emitted README's troubleshooting table and in the tutorial
step whose own prompt is "Why isn't my refund policy in llms.txt?" — verified by
testing both alternatives, each of which breaks the export. -
6c47618: Measure WHICH skill a real agent reaches for, and record what the first sweeps
found.A live walk of the published 0.0.59 reported that
add-sourcesdid not fire on
its headline prompt. The tempting repair is to reword the description until it
does, which is a guess. This is the instrument that replaces the guess: N runs
per phrase, in a fresh scaffold with all three skills present, graded on which
skill the agent actually invoked, across more than one model. Reported, never
gating — a model is stochastic and a threshold over a handful of runs flakes.The model is a column, because the answer depends on it. Same phrase, same
scaffold, same harness:claude-sonnet-5firedadd-sources3/3 where
claude-opus-5fired nothing 0/2. The walk used the CLI default and the first
probe pinned Sonnet, which is why they disagreed — neither was wrong, and
neither alone measured the trigger.The finding is narrower than the walk suggested.
add-sourcesfires on four
of five phrases on both models, and both controls behave: a different skill wins
the intake phrase, and nothing fires on a question about the repo itself. It
misses exactly one shape on Opus — the owner pointing at a file already in the
repo and naming a destination.And the obvious repair does not work. Naming that shape in the description
was tried and measured: unchanged at 0/3. So the cause is not the wording — an
instruction concrete enough to act on gets acted on, and no skill is consulted.
The clause was reverted rather than kept, because it is resident context in
every session and bought nothing a measurement can see. The negative result is
recorded beside the rows it explains.Also fixed while building it: the CLI can emit raw control characters inside its
JSON, whichJSON.parserejects outright — the harness lost the whole transcript
to a stray byte. It now falls back to a scrubbed parse.Test infrastructure only; nothing an adopter installs behaves differently.
Assets 2
@panaversity/ksor@0.0.59
1b2971a Patch Changes
-
4d7dad7: The agent tier authenticates through
claude's own login, never an API key.Where the repository needs model inference — today, the skill evals — it uses
claude -p: a developer's logged-in CLI locally, and in CI a long-lived token
fromclaude setup-tokeninCLAUDE_CODE_OAUTH_TOKEN. The tier no longer reads
ANTHROPIC_API_KEYand no longer passes--bare, because bare mode does not
read the OAuth token (per the official headless docs) and would have quietly
fallen back to needing the key it is not supposed to have. A guard holds both.The pending owner action changes with it: a
CLAUDE_CODE_OAUTH_TOKENrepository
secret, not an API key. Test infrastructure only; nothing an adopter installs
behaves differently. -
5bf3733:
ksor build --bundlesis implemented (issue #158). It used to parse the flag
and exit2; it now runs an ordinary build and additionally writes one OKF
bundle per viewer under.ksor/out/bundles/<viewer>/—public, and
[public, X]for each audience X your.ksor/governance.yamlregisters. A
bundle holds exactly what that viewer's machine surfaces publish: the admitted
concepts (stable, effective, unexpired, not taken down, audience overlapping),
their companions, the assets their bodies reference, and everyindex.md
regenerated for that filtered tree withokf_versionat the root. No byte of a
concept excluded for AUDIENCE reaches it — not a title, a path, a description
or an asset — because the record checker refuses a link that widens audience
before a bundle is planned. A document excluded for a lifecycle or ledger
reason is different, and deliberately: bodies are copied verbatim, never
rewritten, so a link to a draft or a taken-down document keeps that path and
the build reports the dangling link instead of editing your prose. Any OKF
consumer reads a bundle with no ksor in the loop. The directory is replaced on
every run, a copy ofbuild.lock.jsonis written beside the bundles, and the
scaffold's.ksor/*rule already gitignores it.build.lock.jsongainsbundles[]— one{ viewer, sha256, files }per
viewer, recorded on EVERY build whether or not the flag was passed, so a
bundle directory can be matched to the publication that produced it. The
digest is sha256 over the JSON of the bundle's sorted[path, sha256]pairs.
build_idis unchanged: the bundles are a function of what it already
hashes.Upgrading: a lock written by an earlier ksor lacks the key, so
ksor build
refuses it asksor-lock-invalidand says to delete it. It does NOT regenerate
one it cannot read — the lock is also a takedown baseline, and a baseline
nothing can parse is one that quietly holds nothing. So the firstpnpm build
after upgrading (ksor build && <site build>, which is the deploy command too)
is red until the stalebuild.lock.jsonis gone.ksor migratenow offers that
deletion like any other change it carries, so the four steps in
docs/upgrading.mdcover it; deleting the file by hand and rebuilding does the
same thing.Two new refusals, both raised on EVERY build — not only under
--bundles—
because the lock listsbundles[]either way and a digest for a directory the
tool refuses to write would be a provenance claim about something that cannot
exist.ksor-audience-identifier-invalid: a registered audience that cannot
name a bundle directory (../x,.hidden,-x,internal., or
build.lock.json, whose name the lock copy beside the bundles already holds) —
an identifier must start with a letter or a digit, may then use letters, digits,
-,_and., and may not END in., which Windows strips from a path
segment, sointernal.andinternalwould be one directory there.
ksor-audience-identifier-collides: two registered audiences differing only in
case, such asinternalandInternal, which are two viewers in your policy
and ONE directory on macOS and Windows — the second bundle would merge into the
first, leaving a directory that holds concepts its viewer may not read. -
d896f67: The skill harness gains two fixtures a baseline plausibly gets wrong, and is
parametrised over a CASES table. Nothing an adopter installs changes: the
fixtures, the graders and the baseline live undersrc/evals/, outside the
tarball.Three armed runs on the clean two-page PDF had shown both arms — the agent with
add-sourcesand the agent without it — passing every deterministic gate; a
harness that cannot tell the arms apart is measuring the fixture, not the
skill. Soexpense-policy-hard.pdfis built out of the acts a careless
conversion fails a deterministic gate on: a five-row per-diem table (a dropped
row is a valueverify.mjscannot see missing), the payment window stated
twice and differently — "10 working days" in §2, "ten (10) business days" in §5
— which the skill says stays two statements, flagged;1,250beside1.250on
one row, where a misread makes them equal and a substring check cannot tell; a
threshold with a thousands separator; and a running footer on both pages.
scanned-policy.pdfis the same policy rasterised — two pages of picture, no
text layer — for which the skill's instruction is to stop and tell the owner.skill-cases.tsis the table: fixture, prompt, the outcome class a correct run
belongs to (convertedorrefused), and the gates that apply. Its body
gates are pure functions, so a unit suite mutation-tests each one — smoothing
the two statements, misreading the figure, dropping a row, dropping or
misreading a thousands separator, keeping the footer, inventing a currency —
and watches exactly the gate built for it go red. The agent suite also asserts
the fixtures are what they claim: each committed.txtis its PDF's
pdftotext -layoutextraction, and the scanned PDF yields only whitespace. For
the scanned case the WITH arm's correct outcome — wrote nothing, named the
missing text layer — is the pass; what the baseline did is reported.Not run here: the armed arms.
SKILL_BASELINEcarries no row for either new
fixture; the hard fixture's first armed run is pending and its row lands with
the run that produces it.RESULTS
-
3ba4acc:
ksor-generated-stale: the first change-control verification (KSP R23). Astabledocument whose body differs from a committed version that wasstableis now refused byksor buildandksor ingestunless itsgenerated.atis strictly LATER than that version's (the scaffold'spnpm checkstays the format gate and does not run it), naming the commit whose body differs, its instant and the stamp. Leaving the stamp alone and moving it BACKWARD are refused alike: neither advances it, and a backdated stamp leaves the old approval post-dating the new text — the exact reading R23 exists to prevent. The fix it prints is the whole of it: setgenerated.atto an instant after the edit, then re-approve, becauseksor.approval.atmay not precede it (ksor-generated-after-approval, unchanged). Only the body is compared, so adding averifiedentry or re-approving is not an edit; a document stable for the first time, or renamed, has no history to compare and passes; every committed version is read, so an edit committed without a bump is refused on a clean tree too.Where history cannot be read — no repository, no commit yet, a container without
.git, a shallow clone — each verb printschange-control: not checked(or how many versions a shallow clone let it read) beside its verdict instead of passing. Who approved is still checked against the policy alone: every envelope keepsapproval.checked: "policy"until R22/R25 have an identity source to verify against. -
db39c11: Switch Next.js telemetry off in the scaffold, and make the README describe the
scaffold that ships.Nothing phones home — now true of the artifact, not only of the pitch.
Next.js ships with anonymous usage telemetry ON, so every siteksor init
emitted posted totelemetry.nextjs.orgon its firstbuildanddevwhile
the README promised nothing phones home. The site's two scripts now run next
throughsystem/site/next-no-telemetry.mjs, a dependency-free wrapper that
hands it an environment carryingNEXT_TELEMETRY_DISABLED=1; that is where
every package manager's root script and the deploy'spnpm buildend up, and
.env.examplesays so. It is the process environment rather than
next.config.mjson purpose: read in 16.3.3's source,next buildconstructs
its telemetry after loading the config, but thenext devparent process never
loads the config and still records a session event on exit — a config-time
assignment would have left that one in place with nothing red. And it is a file
rather than aNEXT_TELEMETRY_DISABLED=1 next …prefix inpackage.json
because that prefix is POSIX shell syntax: cmd.exe refuses it under pnpm and
npm on Windows, a platform CI walksksor initon, so the prefix would have
traded a quiet leak for a loud break. An existing scaffold gets the switch from
ksor migrate --write-site, which offers the wrapper and the two scripts as a
diff.The README no longer contradicts the tree. Five sentences described a
product that does not ship, each now corrected and held by a test against the
thing that decides it:pnpm serveisksor servealone (provisioning is
pnpm provision, publishing ispnpm refresh, and ingest reportsunchanged
only when documents, governance, toolchain AND source commit all match the
serving generation); the gate paragraph names the browser, Postgres, Windows,
npm/bun and container acceptance CI has run for weeks instead of promising
them, two-way against the workflow's job names; an authoredlog.mdis refused
(ksor-reserved-name) and never generated, whileindex.mdis generated ...
Assets 2
@panaversity/ksor@0.0.58
851a448 Patch Changes
-
3807493: Twenty-six sentences that 0.0.44 through 0.0.57 had made false, found by a
sweep of every document against the tree and the published package, and fixed.The two that ship on npm and would have stopped a reader: the package README
toldnpxusers to runpnpm install— on the npm projectnpxemits, that
installs nothing undersystem/siteandpnpm devfails withnext: command not found(the tutorial had this exact fix; the README never got it) — and it
still advertised the two companion skills 0.0.56 removed. It also claimed its
abstention envelope was "pasted as it appeared" from a tutorial that never
reaches one; it now says what that block is.The rest:
calibrate --checkwas documented in three places (ingesting.md,
the CLI help, and by implication the scaffold) as "always exits 0" — a verdict
always does; an unreachable database exits 3 as for every verb, and the docs
now say so before anyone puts it in CI.tool-surface.mdcarried two copies of
one bullet, the stale one missingaudit.upgrading.mdgaveksor inita
path, which it refuses. Three documents said "two" authorization recipes where
authorization.mdhas four. The emittedinstance.mdstill described "all
four study attachments" on a starter trimmed to one companion in 0.0.44;
intake-interviewandpeople.yamlsaid add-sources writes display names (it
does not); the scaffold README counted two negated.ksor/paths where there
are three; two token figures in the emitted AGENTS.md predated their own
re-measurement; and a sentence about "this record's own seven" embeds
described a record that is not the one it is emitted into. Root AGENTS.md said
"three tiers" above a list of four.docs/status.mddescribed the seed quiz
and deck as shipping (trimmed in 0.0.44), the kernel as "in progress" (released
in 0.0.8–0.0.18), and carried yesterday's date. The introduction tutorial's
"where to go next" now links the two tutorials that exist and numbers the rest
as the index does; hello world said "five skills"; make-it-yours saidpnpm dev
in an npm project.Every fix is a document catching up to code, except the CLI help line, which
is text.
Assets 2
@panaversity/ksor@0.0.57
2c25c78 Patch Changes
-
07cddea: The tutorials are numbered in reading order, and the package README lists all
three.The introduction is now
00, hello world01, and Make it yours stays02
— so the two hands-on tutorials that follow each other are no longer separated
by the 1,800-line introduction, and every existing cross-reference ("tutorial
2", "tutorial 4") stays true. The introduction's title drops "Tutorial 1"; it is
the introduction. A one-line pointer stays at the old00-hello-world.mdfor
one release, because the README that shipped in 0.0.56 links it.Both READMEs gain a three-row table keyed on who each tutorial is for, and a
test now holds every link intodocs/tutorials/— relative or GitHub URL —
to a file that exists.
Assets 2
@panaversity/ksor@0.0.56
916392c Patch Changes
-
bf35e2f:
add-sources2.0.0: a file or a person, one skill — with a check the agent
runs instead of a rule it is asked to follow.Two issues asked for two paths into the record. #31: an owner with a folder of
PDFs had no path — the skill stated the rules and converted nothing. #50: an
owner whose knowledge is only in their head had no path — the interview scoped
the record and stopped, leavingknowledge/full of samples about KSoR.They are one skill, not two. The record draws no line between the kinds:
an interview attestation insources[].resourcepassesksor buildtoday,
the fidelity rules read the same for both, and a real owner has BOTH — the
policy PDF and the exception everyone knows that the PDF never mentions. So
the person step runs after every file: "what does this not cover?" is the
question that finds the pages nobody wrote. A sibling skill would have made
the agent choose before it knew, and #50's own three-way trigger collision
vanishes.The source is a file. Extract first, into a scratch file outside
knowledge/— the skill names the extractor per format (pdftotext,
pandoc, macOStextutil,markitdown) and what to do with none onPATH:
read the file directly and SAY the verification that follows is weaker. An
empty extraction is a scanned image, and the skill stops and tells the owner
rather than OCR and hope. Then decide the shape of the record, convert to
CommonMark a person would have written, name the source precisely, and run
the shipped check:node .agents/skills/add-sources/verify.mjs /tmp/in.txt knowledge/<path>.mdverify.mjs— plain Node, no dependencies, the owner's to keep — lists every
number, date, threshold, code and capitalised name in the document's body that
does not appear in the extraction, case-folded and whitespace-collapsed.
Frontmatter and footnote ids are exempt, because they are the agent's words by
design. It is a floor, and says so: a value that passes was in the source; it
cannot see a value that was dropped, and it cannot tell a paraphrase from an
invention. Model-driven conversion is highest-fidelity for layout and
lowest for exact values, and this is the mechanical half of "copy load-bearing
values exactly".The source is a person. Ask one question at a time in their words — who
triggers it, the steps, who approves and at what threshold, what goes wrong,
the exception — until someone who was not in the room could act on it. Draft
as the record, not a transcript; anything unconfirmed is anOpen question:
line, never prose. The attestation goes insources(who, role, instant,
conducted by) — there is noprovenance:key, and no transcript is kept.
Two people describing one process differently stay two cited statements.Both end the same way, and the ending is new. Read it back on
pnpm dev,
then ask the owner to approve and write down what they said. A draft reaches
no machine surface, so a skill that stopped at the draft left everyllms.txt
and every door empty — found on the journey walk:1 document(s), 0 admitted.The rules that were restated here (placement, frontmatter, audience,
deprecation) now point at the emitted AGENTS.md instead. The trigger test that
asserted "no skill claims dictated knowledge" flips: add-sources claims it by
name, and no other skill may. Eleven cases holdverify.mjsto what it does
and, in three of them, to what it does not claim. -
73530e6: Prune the scaffold's skills to the three that make a record, and fix the two
seams every adopter hits on the way to one.Removed:
make-slidesandmake-summary. They were 45% of all shipped
skill text and 21.6% identical to each other — their own commit says
"make-summary is make-slides' discipline applied to prose". A companion is
downstream of a record existing and invisible to the agent surface (no route,
nollms.txtline, no MCP node); no fixture and no tutorial ever fired either;
and neither was ever shown to beat its absence, which is the bar AGENTS.md sets
for keeping a skill at all. Their one real rule — a card may only say what its
document says — already lives in the emitted AGENTS.md, and the site renders
companions exactly as before. Verified: the emitted checker passes with both
gone.format-checker/SKILL.mdis cut to what AGENTS.md does not say; the
program it names is unchanged.Fixed:
intake-interviewwas contradicting itself in its trigger. The
always-resident description promised "seven questions"; the body has asked
three since 2026-08-26. The body handed off to "question 4" and "question 5",
neither of which exists, and claimedadd-sourceswritesverified:entries —
nothing does. 1.6.0 says three, hands off to add-sources with whatever material
the owner has, and drops the false claim.Fixed: the recommended path turned a green record red. Walked on the
published package:- The README said run the interview, then "delete each starter as your own
knowledge arrives". Delete the five first and the build refuses
ksor-record-emptyand writes nothing — a slug named by no document an
adopter reads. The README now says to write and approve one document of
your own before the last starter goes, and names the refusal. - The hello-world tutorial approves as
human:you. The interview then retires
human:youfrom the policy, and the tutorial's own document — still approved
by an actor the policy no longer names — refusesksor-approver-unauthorised.
The interview now re-attributes every act recorded under the placeholder to
the owner's handle in the same change: it is the same person.
Consolidated: "the record says only what its source says — a gap is an
open question, never filled from general knowledge" lived only inside
add-sources; it is now stated once in the emitted AGENTS.md where every
other writing rule is.Two deterministic gates hold all of this: a skill-consistency lint (a trigger's
question count matches its body; every "question N" resolves; every refusal
slug a document names is one the product raises; every skill a document names
ships) and a journey walk against the built CLI (interview → one draft →
approve → delete starters → retire the starter actor, plus the exact state
each refusal fires on). Every lint assertion was mutation-tested.Found by four independent reviews of the plan to build a system of record,
before building any of it. - The README said run the interview, then "delete each starter as your own
-
ab6a3ed: The agent tier: a shipped skill, run by a real coding agent, with the skill
and without it — the comparison AGENTS.md has always demanded and nothing had
ever run (issue #30).pnpm test:agentscaffolds a fresh record, installs it, drops a real two-page
PDF insrc/, and handsclaude -pthe prompt tutorial 2 hands the reader.
Once withadd-sourcespresent, once with it removed. What the agent leaves
behind is graded, and the split is the Testing contract's own: deterministic
behavioural graders GATE the with-skill arm — exactly one new document, under
finance/;.ksor/*andinstance.mduntouched; the record builds;status: draft,sourcespresent, noid:/name:; page furniture gone; every
number, date and name in the body found in the extraction by the shipped
verify.mjs. Cost, turns, duration and the baseline arm are REPORTED, so the
delta is visible and a skill that stops winning is seen."Checker passes" is deliberately not a grader: while this was being designed a
baseline run passed the checker by hand-authoringindex.mdand editing
.ksor/people.yaml— the worse behaviour scoring better. Files touched is the
discriminating assertion.It spends model tokens, so it is gated like the database tier: on
ANTHROPIC_API_KEYin CI (a repository secret the owner has not yet added; the
tier runs and reports itself skipped until then) or a logged-inclaude
locally, pins a mid-tier model by default (a one-word reply on the default
model measured $0.25), and runs fromskill-evals.ymlon push to main and by
hand — never per pull request.What it cannot measure is written in the suite rather than implied: a
conversational skill needs a scripted owner, "reads as a finished page" needs
a browser, and the adopter's own model is whatever they run.Decision 31 records the three choices this week made about the skill surface —
pruned to three, one skill for a file and a person, and this harness shape
over the Python trigger script that was proposed and measured wanting. -
c34cc3a: Tutorial 2, Make it yours: the walk from hello world's record to one that is
only the owner's — every output run and pasted as it appeared.The intake interview and what it does to the placeholder approver; one policy
brought in from a real PDF, with the shipped check catching the one number the
conversion got wrong; one procedure that only ever lived in someone's head,
written with the thing they were not sure of as an open question rather than
prose; the read-back on the site and the approval act; then the samples go and
the tool that approved them leaves the policy. Two refusals do work on the way,
and the tutorial says exactly which state each fires on.The prompt-accounting test now covers both tutorials from one table, so a new
prompt in either fails until someone names the skill that answers it. Only that
test changes underpackages/; nothing an adopter installs behaves differently. -
87a3542: Correct a claim the tutorial made about
build_id, and guard the general rule.buildIdOfhashesksor_versionalong with the record — deliberately, because
"what produced this" is part of what a publication is. So a capturedbuild_id
is correct for exactly one release, and the sentence 0.0.54's tutorial fix added
— "Yo...
Assets 2
@panaversity/ksor@0.0.55
a83decd Patch Changes
-
4ecf549: Test infrastructure only — nothing an adopter installs behaves differently.
The behavioural evals scored a missing
top_cosineas-1. When a provider
rate-limits, the read plane degrades to keyword-only by design, so searches
answer with no score — and the assertions then compared sentinels, reporting a
vendor outage as "the abstention floor is broken". Four CI failures in a day
read that way before anyone looked past the assertion. A missing score now
refuses, naming the cause, and never invents the number that is absent. -
e476197: Fix the hello-world tutorial, which could not be completed as written.
Three defects, all found by walking the published 0.0.54 rather than by reading:
- Step 3's document declared
type: Policy.Policyis a reserved type, so the
record demandssources—ksor build,npm run checkand the dev server all
refused it, and steps 4 through 10 were unreachable. It is nowtype: Document,
the type the profile promises never to reserve, with a note on why and on what
an agent should do when it reaches for a reserved one. - Step 1 scaffolds with
npx, which emits an npm project, and every command
after it saidpnpm. On that projectpnpm install && pnpm devfails with
sh: next: command not found. All sixteen commands are npm's now, and the step
that explains manager detection says which one the rest of the tutorial speaks. - The captured outputs had been trimmed after capture, in a document whose second
paragraph promises they were "pasted as it appeared":ksor serve's boot report
was missing thetrustline it has always printed, the build outputs were
missing their timestamp,source:andwrotelines, and the port-conflict
refusal was quoted offeringpnpm servewhere it saysksor serve.
The walk also surfaced that
ksor initleaves a repo with no commits, so every
reader's first build printssource: unspecified. Rather than hide it, the
tutorial now shows it and foldsgit commitinto the approval step — which is
where provenance belongs anyway, and which lets the second build print a real
commit sha.The tutorial also said
.mcp.json's "first is Neon's" and named the second
server nowhere, and said nothing about the Neon server acting on the whole Neon
account. Both are fixed here for the tutorial; the emitted scaffold's copies of
the same two defects are fixed separately.Only the tutorial and the test that pins its prompts changed; nothing an adopter
installs behaves differently. - Step 3's document declared
-
ae49524: Stop a spent OpenAI balance from quarantining content and flipping a generation,
and name the right variable when a provider key is missing.The serious one.
insufficient_quota— OpenAI's answer to an exhausted
balance, which arrives as 429 like an ordinary rate limit — was classified
non-retryable, correctly, because no amount of waiting adds credit. But
"non-retryable" is what the ingest drain reads as poison chunk: it
binary-splits the batch down to singletons and marks eachfailed. A spent
balance arrives on every chunk, so a run walked the queue quarantining
everything it touched; if the failed fraction stayed under
MAX_FAILED_FRACTION(2%),generationReadyadmitted it and the generation
flipped — publishing a record in which exactly the passages the owner had
just edited were unsearchable,ksor ingestexit 0, the billing reason visible
only inchunks.embed_error. The same event on Gemini aborts the run, so
switching provider silently changed what a spent quota does.The drain now has three answers instead of two: retryable (abort, chunks stay
pending), fatal (abort the same way, but without spending five backoffs
first — the account is what is wrong, not the passage), and everything else
(binary-split to the poison chunk).isFatalis optional onEmbeddingProvider,
so a provider that cannot tell keeps the old two-kind behaviour and Gemini's
path is unchanged.The missing-key refusal names the variable.
ksor serveon an
embedding.provider: openairecord saidembedding provider "openai" needs an API key and none was suppliedand stopped — whileksor serve --help,
env.exampleanddocs/deploying.mdall namedGEMINI_API_KEY, which that
door does not read. The registry row already heldkeyEnv; it now reaches the
operator (— set OPENAI_API_KEY), and all three documents describe the choice
instead of one vendor.ksor calibrate's Gemini requirement is stated rather than papered over.
Question synthesis is Gemini-only today, so a record embedding with
OPENAI_API_KEYis still refused for a Google key when calibrating through the
synthesized door. That gap is now said plainly in the refusal and in
docs/ingesting.md, which taught calibration without mentioning it. The
--queries-filedoor avoids it entirely.The OpenAI live test announces itself. It is gated on
OPENAI_API_KEY, no
workflow supplied one, and a falsedescribe.runIfcontributes nothing to a run
— so the suite its own header calls "the tripwire for vendor drift" was absent
from CI and reported as absent by nobody. It now printsskipped — set OPENAI_API_KEY, the way Gemini's does, and CI passes the secret so the tripwire
arms the moment one is added.Found by an adversarial review of this week's commits.
-
ff99eb5: Hash
.ksor/people.yamlintobuild_id, so the two surfaces of one build
cannot publish different provenance.The phone book added in 0.0.53 rewrites the actor printed on every Owner,
Approved, Withdrawn and Trust row —displayActorreplaceshuman:contractor-a
with "Human: Jane Doe, VP Compliance", and the identifier does not appear on the
page at all. It was hashed by nothing..ksor/governance.yamland
.ksor/takedowns.yamlare both inbuild_id; this one was left out, on the
stated reasoning that including it would refuse the next site build after a
spelling correction.That is the trade critical rule 1 forbids, and the consequence was reachable
without doing anything unusual: edit a name,pnpm checkstays green,
ksor buildemits a byte-identical lock, and the deployed page publishes an
approver the/md/twin stamped with that samebuild_idcontradicts. An
auditor reconciling the page against the lock finds nothing wrong, because the
string they are auditing was never covered by it.Now:
people_sha256joinspolicy_sha256andledger_sha256in the lock and
inbuild_id;.ksor/people.yamljoins the inputs that movesource_commit;
and the site's staleness gate compares it like the other three, so an edit the
lock never saw refuses withksor-lock-stalenaming the file. Refusing until
ksor buildis re-run is the behaviour, not a regression — it is what every
other published byte already does.Two things found alongside it, in the same file:
people.tsclaimed "duplicate keys are refused by the parser rather than
resolved by whichever came last". They were not.uniqueKeys: truemakes the
parser RECORD a duplicate;toJS()still resolves last-wins, and nothing read
the errors — so two entries for one actor published the second person's name
on the first person's approval, the precise collision the map replaced a name
derivation to avoid. A duplicate now drops the whole book, and identifiers are
published instead.- The rule lived behind a module that reads
instance.mdon import, so it could
only be tested by building a record on disk — which is why it shipped asserted
by a comment. It is now a leaf,lib/people-rule.ts, with the shipped
function under test.
Upgrading: a lock written before this refuses with
ksor-lock-invalid
namingpeople_sha256; runksor buildand commit the lock it writes.Found by an adversarial review of this week's commits.
-
b45d477: Say what the scaffold's
.mcp.jsonattaches to an adopter's coding agent, and
stop the README telling them to destroy it.ksor initemits.mcp.jsonwith two servers. The emitted README and AGENTS.md
both said "the first is Neon" and named the second nowhere — so
agentfactory-system-of-record, a Panaversity-operated endpoint, was wired into
every adopter's coding agent with no emitted document mentioning it..mcp.json
attaches servers to the agent that OPERATES the record; a server nobody
documented is a capability nobody reviewed.Both are now named, with what each is and that either may be deleted. The second
is described as what it is: a read-only example record that is not the
adopter's and that nothing in the project depends on.The Neon step also said only that the server exists. It acts on the Neon
account — an agent holding it can create and delete projects and branches — so
the README and AGENTS.md now say that before handing over a prompt that runs
against real infrastructure, and point at Neon's own documentation for the
scopes rather than paraphrasing them.And the "Test the door with an actual agent" section told the adopter to write
.mcp.jsonwith a file containing onlytest-record— overwriting the Neon
entry the same README depends on two sections earlier — and then closed with
"Delete.mcp.json, or keep it". It now shows the entry to add, and says not
to delete the file.A guard derived from
mcp.jsonitself asserts every server key appears in both
emitted documents, so adding a server and saying nothing fails on the server
that was added. Mutation-tested: unnaming the second server turns both red.Found by an adversarial review of this week's commits. Whether the scaffold
should ship a second, vendor-operated MCP record at all is an owner question and
is untouched here. -
5283084: Test infrastructure only — nothing an adopte...
Assets 2
@panaversity/ksor@0.0.54
3d087ab Patch Changes
-
3f55e91:
ksor calibratenames the free-tier path when a quota refuses it, and the
calibration text model moves togemini-3.7-flash.Walked on a real free-tier key: embedding is free of charge and a first corpus
embeds fine (23 chunks, 0 failed), but the DEFAULT calibration door writes one
probe question per sampled passage with an LLM — and the free tier allows five
generations a minute. So the documented way to turn on the product's headline
feature failed, surfacing the vendor's sentence and nothing else.Two quotas reach that code and they need opposite answers: the generation cap is
a wall no wait clears (the remedy is--queries-file, the zero-LLM door), and
the embedding cap is a per-minute window (the remedy is to wait, and the usual
cause is an ingest immediately before). Both are now named, with why. A 429 this
does not recognise is re-thrown untouched — an invented remedy is worse than the
vendor's own message.docs/ingesting.mddocuments the zero-LLM door where the reader meets the
command, including how to choose the questions: the floor is set by the weakest
one, so a vague question drags it down and a question the record cannot answer
invalidates the measurement.The text model moves
gemini-2.5-flash→gemini-3.7-flash. Cheap, unlike the
embedding model: it only writes probe questions, so nothing stored is
re-computed and no floor is invalidated — and the door is recorded beside every
number, which is what stops two measurements being compared as one experiment.And calibration now embeds on the patient retry. It used the READ plane's
door, which never retries a 429 — correct for a live search, which should
degrade to keyword-only in under a second rather than stall a reader behind
backoff, and wrong for a measurement nobody is waiting on. So a free-tier key
that rate-limited mid-run refused the whole calibration. The intent stays
query(a floor must be measured through the label the door searches with);
only the retry policy moves, to the oneisRetryable's own comment describes
for batch work. Calibration's text generation already took that path, so this
was the two halves of one act disagreeing. -
dd6371b: Two things: a false claim removed from a shipped page, and the scaffold gains
.mcp.json.The false claim.
docs/deploying.mdtold adopters "The MCP surface already
applies the audience scope per request", under the heading of the very
requirement it does not meet. It does not:content-gateway/src/compose.ts
readsKSOR_AUDIENCEfrom the environment once at boot into a per-process
viewer, and the request path never touches it —docs/authorization.mdsays so
plainly ("Any caller holding a valid token gets the whole record") and
specs/ksor/serve/spec.mdnames per-request visibility filtering as out of
scope. A reader who believed the page would point every caller at one door and
serve them the restricted half. The page now says what the door does — one
viewer per door, so one process per audience — and separates the audit it does
give (aretrieval_logrow naming the verified caller) from the authorization
it does not. A docs-truth assertion now fails on the claim itself, not merely on
a command that no longer exists..mcp.json. The scaffold's closed root set gains one member: the MCP
servers a coding agent may reach from the project. It ships with Neon's, which
turns the step the tool could never do for an adopter — provision a Postgres,
enable pgvector, produce a connection string — into four real tool calls
(create_project,run_sql,create_branch,get_connection_string) and one
sentence to the agent. The scaffold's README and AGENTS.md carry that sentence,
and both now say plainly which step no agent can do at any price: the embedding
API key, which no vendor mints over a protocol. Committed rather than ignored,
because both entries authenticate interactively and the file carries no secret —
stated, because pasting an API key into it would change that. -
eebd777: OpenAI joins Gemini behind the embedding seam, and the wiring stops naming one
vendor (issue #25).The seam was already vendor-neutral in shape —
EmbeddingProvider, the
framework's normalization and degeneracy checks, and an embedding space
identified bymodelId+ column width and never by the vendor. What was
Gemini-bound was the WIRING:GEMINI_API_KEYwas spelled into three composition
roots, so a second provider could not obtain a key even though the registry
would happily build it. Each registry row now names its own key variable, and
the roots ask, exactly asinstance.mdnames the DSN variable rather than
hardcoding it.provider: openaiwithmodel: text-embedding-3-smallanddim: 1536reads
OPENAI_API_KEY. Overfetch, no SDK — the same call decision 12's 2026-08-22
revision made for Gemini, and for the same reason.Two things a live call surfaced that a stub would not. Response items carry
their ownindexand the vendor does not promise array order, so they are
sorted before the framework pairs them positionally — a shuffled response is the
same count, the same width and all finite, so every downstream check passes
while every passage carries another's vector. And an exhausted balance arrives
as 429, the same status as a rate limit: it is now read from the vendor's
error.typeand never retried, because five exponential backoffs do not add
credit.Switching provider is a re-embed of the whole corpus and a re-measured
vector_floor. A different provider is a different embedding space, and the
invariant against copying a calibrated constant applies across vendors with more
force, not less.Verified live against the real API on a funded key: 1536-dimension vectors, a
paraphrase at cosine 0.812 against an unrelated sentence at 0.058, and the two
intents agreeing to 0.9997 — which is the symmetry that makes the empty task
labels correct. Then through the whole plane: a real record ingested to Postgres
underembedding_model = text-embedding-3-small, 23 chunks, 0 failed, stored at
the declared width of 1536 and L2-normalized as the framework promises.One more defect the live call found:
buildShippedProviderhanded EVERY
provider Gemini's task labels from global config, so an OpenAI run logged its
space astext-embedding-3-small/d1536/RETRIEVAL_DOCUMENT— a label that vendor
has no concept of and never received. The labels moved onto the registry row,
where a vendor's shape belongs. -
eb54871: The npm page shows what the product does, on the first screen.
It asserted the headline behaviour — a cited answer, an honest refusal — and
demonstrated it nowhere. A reader had no way to tell a real mechanism from a
prompt instruction, which is exactly the skepticism this product exists to
answer. It now shows three things, all of them real output: the admitted count
moving when a human approves a draft, theprovenanceandgovernancea search
hit carries, and an abstention envelope. It also links the hello world.Nothing was added to the tarball; this is the README npm renders.
-
6e5ff3e: Repo documentation and a test only — nothing an adopter installs changes.
docs/status.mdnamed 0.0.42 while the published package was 0.0.53. Authority
rule 3 makes that file the only authority on what is built, and it is the first
thing an evaluator's coding agent reads. It is current now, and a docs-truth
assertion holds it equal topackages/ksor/package.jsonso a Version PR cannot
bump one without the other. It also records that the full kernel walk was re-run
against 0.0.53 — it had last run against 0.0.18, thirty-five releases earlier. -
ed947c1: The deploy runbook stops ruling out the one step Vercel calls required.
docs/deploying.mdsaid the silent-404 failure "does not depend on the
Application Preset". Vercel's own guide says the opposite: a project builds as
services only when the preset isServicesANDvercel.jsoncarries a
serviceskey, and "if either is missing, Vercel falls back to its default
framework detection and ignores your services configuration" — which is that
failure exactly, and no file in the repository can set a project setting.One measurement of ours disagrees with that guide and is recorded rather than
reconciled: two projects read back from the API, oneServicesand oneOther,
both built and served. Both facts are real; guessing between them is what
produced the sentence that steered adopters away from the fix.The scaffold's runbook now sets the preset at step 2, and ends with the three
curls that tell a live deployment from a Ready-and-404 one —/mcpanswering
405 is the door refusing a GET, which is how you know it is routed at all.
Assets 2
@panaversity/ksor@0.0.53
ea37a12 Patch Changes
-
f77ca55: The emitted README documents
.ksor/people.yaml.Display names shipped documented in the scaffold's AGENTS.md and not in its
README — so the coding agent knew about the feature and the owner did not, on
the one feature whose entire input is a human typing their colleagues' names.
The README now carries it beside the keys that publish a document: the map
shape, that an actor with no entry renders exactly as stored, and that
appearing in the file grants no authority at all.
Assets 2
@panaversity/ksor@0.0.52
4fdbcb7 Patch Changes
-
1e60b9d: The site prints natural names for the actors a record cites, where the record
declares them.A governed page led with a slug — "Owner · human:bashiraziz" — on every owner,
trust, approval and withdrawal line..ksor/people.yamlmaps an actor to the
name a page should print, and the site substitutes it at render time. An actor
with no entry renders exactly as stored: no splitting, no camelCase guessing,
no derivation. A display name is the one thing in a governance line that cannot
be computed —bashirazizis equally "Bashir Aziz" and "Bashira Ziz" — so the
owner is its only source.A MAP keyed by the whole identifier, not a list of names a handle is derived
from. The derivation could only ever match a handle that IS somebody's squashed
full name, sohuman:ciso,human:audit-leadandhuman:mjs— most of the
actors in a real record — had no expressible name at all; and it collided, since
two different people can squash to one handle.Deliberately NOT part of
.ksor/governance.yaml. That file is the root of
authority: its key set is closed so nothing can sit there without being
enforced, and its digest is hashed intobuild.lock.json— so a display name
living there would mean correcting the spelling of somebody's name refused the
next site build asksor-lock-stale. Appearing inpeople.yamlconfers no
authority; it only changes what is printed, and nothing cross-checks the two
lists, because a person who leaves the authority list is still the recorded
approver of everything they approved. -
f23cddc:
ksor calibrate --checkreports whether a declared abstention floor is still
holding, from the record's own traffic.A floor is measured once and the record then grows. As it does, questions that
used to be out-of-corpus start scoring above a fixed number, so the record
answers what it used to refuse — no error, nothing logged, and the same
gate: { floor: … }in every envelope. AGENTS.md forbids copying a calibrated
constant between corpora; the same reasoning applies across time within one
corpus, and nothing enforced it (#182).It needs no telemetry and no new dependency: every search already leaves an
audit row carrying the gate's own signal, on both sides of the gate, so this is
one indexed query — no provider key, no embedding call, no LLM. It reports the
abstain rate, the percentiles of answered top scores, and how many answers
landed within 0.01 of the floor (the size of the decision in this project's own
gold, not a threshold somebody picked).It never fails a run, and that is the design rather than a limitation. A
stale floor wants re-measuring; failing a build for one would make the shortest
way out deletingvector_floor— turning the abstention gate off entirely to
clear the error, which is the escapebuild/lifecycle-notice.tsrefuses to
create for a passed review date. It is also a monitor and not a measurement: it
can say a floor has gone permissive against real traffic, never that it is too
strict for questions nobody asked, and it says so rather than reporting a
healthy-looking nothing on a record no one queries. -
a403e19: the served envelope now discloses when a §7 audit row could not be written (issue #150)