Skip to content

Releases: panaversity/ksor

@panaversity/ksor@0.0.61

Choose a tag to compare

@github-actions github-actions released this 01 Oct 13:18
b50f289

Patch Changes

  • c8df2a0: pnpm dev now 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 found for 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
    reissues system/site/lib/stage-knowledge.ts with 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
    of fumadocs-core@16.15.4, the version the scaffold pins. Any page with bold or
    italic text sent it into recursion without end, so npm run build and
    bun run build failed with RangeError: 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 root package.json now 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
    same overrides entry to your root package.json and install again. Remove the
    entry when you move fumadocs-core to 16.15.15 or later, which has the
    upstream fix.

@panaversity/ksor@0.0.60

Choose a tag to compare

@github-actions github-actions released this 03 Sep 06:51
cca9df8

Patch Changes

  • 2a7ef92: Five things a live walk of the published 0.0.59 found, fixed.

    verify.mjs reported ordinary markdown as an invented name. Its name regex
    let \s+ cross a blank line, so a ## Meals heading followed by a paragraph
    opening On 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 init runs git init and leaves zero commits, and its own epilogue went
    install → dev → provision → refresh with no commit in between — so every
    adopter's first publish said source: unspecified and 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 build recomputes
    every bundles[].sha256 in the lock but only --bundles writes the directory,
    so after one --bundles run 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 omitted change-control: not checked on 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.txt does not change after an approval. Not a bug and
    not fixable in the route: output: "export" requires a static route handler, so
    pnpm dev computes 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-sources did 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-5 fired add-sources 3/3 where
    claude-opus-5 fired 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-sources fires 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, which JSON.parse rejects 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.

@panaversity/ksor@0.0.59

Choose a tag to compare

@github-actions github-actions released this 02 Sep 22:28
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
    from claude setup-token in CLAUDE_CODE_OAUTH_TOKEN. The tier no longer reads
    ANTHROPIC_API_KEY and 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_TOKEN repository
    secret, not an API key. Test infrastructure only; nothing an adopter installs
    behaves differently.

  • 5bf3733: ksor build --bundles is implemented (issue #158). It used to parse the flag
    and exit 2; 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.yaml registers. 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 every index.md
    regenerated for that filtered tree with okf_version at 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 of build.lock.json is written beside the bundles, and the
    scaffold's .ksor/* rule already gitignores it.

    build.lock.json gains bundles[] — 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_id is 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 as ksor-lock-invalid and 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 first pnpm build
    after upgrading (ksor build && <site build>, which is the deploy command too)
    is red until the stale build.lock.json is gone. ksor migrate now offers that
    deletion like any other change it carries, so the four steps in
    docs/upgrading.md cover 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 lists bundles[] 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, so internal. and internal would be one directory there.
    ksor-audience-identifier-collides: two registered audiences differing only in
    case, such as internal and Internal, 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 under src/evals/, outside the
    tarball.

    Three armed runs on the clean two-page PDF had shown both arms — the agent with
    add-sources and the agent without it — passing every deterministic gate; a
    harness that cannot tell the arms apart is measuring the fixture, not the
    skill. So expense-policy-hard.pdf is built out of the acts a careless
    conversion fails a deterministic gate on: a five-row per-diem table (a dropped
    row is a value verify.mjs cannot 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,250 beside 1.250 on
    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.pdf is 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.ts is the table: fixture, prompt, the outcome class a correct run
    belongs to (converted or refused), 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 .txt is its PDF's
    pdftotext -layout extraction, 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_BASELINE carries 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). A stable document whose body differs from a committed version that was stable is now refused by ksor build and ksor ingest unless its generated.at is strictly LATER than that version's (the scaffold's pnpm check stays 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: set generated.at to an instant after the edit, then re-approve, because ksor.approval.at may not precede it (ksor-generated-after-approval, unchanged). Only the body is compared, so adding a verified entry 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 prints change-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 keeps approval.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 site ksor init
    emitted posted to telemetry.nextjs.org on its first build and dev while
    the README promised nothing phones home. The site's two scripts now run next
    through system/site/next-no-telemetry.mjs, a dependency-free wrapper that
    hands it an environment carrying NEXT_TELEMETRY_DISABLED=1; that is where
    every package manager's root script and the deploy's pnpm build end up, and
    .env.example says so. It is the process environment rather than
    next.config.mjs on purpose: read in 16.3.3's source, next build constructs
    its telemetry after loading the config, but the next dev parent 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 a NEXT_TELEMETRY_DISABLED=1 next … prefix in package.json
    because that prefix is POSIX shell syntax: cmd.exe refuses it under pnpm and
    npm on Windows, a platform CI walks ksor init on, 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 serve is ksor serve alone (provisioning is
    pnpm provision, publishing is pnpm refresh, and ingest reports unchanged
    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 authored log.md is refused
    (ksor-reserved-name) and never generated, while index.md is generated ...

Read more

@panaversity/ksor@0.0.58

Choose a tag to compare

@github-actions github-actions released this 02 Sep 07:57
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
    told npx users to run pnpm install — on the npm project npx emits, that
    installs nothing under system/site and pnpm dev fails with next: 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 --check was 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.md carried two copies of
    one bullet, the stale one missing audit. upgrading.md gave ksor init a
    path, which it refuses. Three documents said "two" authorization recipes where
    authorization.md has four. The emitted instance.md still described "all
    four study attachments" on a starter trimmed to one companion in 0.0.44;
    intake-interview and people.yaml said 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.md described 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 said pnpm dev
    in an npm project.

    Every fix is a document catching up to code, except the CLI help line, which
    is text.

@panaversity/ksor@0.0.57

Choose a tag to compare

@github-actions github-actions released this 02 Sep 07:12
2c25c78

Patch Changes

  • 07cddea: The tutorials are numbered in reading order, and the package README lists all
    three.

    The introduction is now 00, hello world 01, and Make it yours stays 02
    — 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 old 00-hello-world.md for
    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 into docs/tutorials/ — relative or GitHub URL —
    to a file that exists.

@panaversity/ksor@0.0.56

Choose a tag to compare

@github-actions github-actions released this 02 Sep 06:06
916392c

Patch Changes

  • bf35e2f: add-sources 2.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, leaving knowledge/ full of samples about KSoR.

    They are one skill, not two. The record draws no line between the kinds:
    an interview attestation in sources[].resource passes ksor build today,
    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, macOS textutil, markitdown) and what to do with none on PATH:
    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>.md
    

    verify.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 an Open question:
    line, never prose. The attestation goes in sources (who, role, instant,
    conducted by) — there is no provenance: 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 every llms.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 hold verify.mjs to 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-slides and make-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,
    no llms.txt line, 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.md is cut to what AGENTS.md does not say; the
    program it names is unchanged.

    Fixed: intake-interview was 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 claimed add-sources writes verified: 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-empty and 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:you from the policy, and the tutorial's own document — still approved
      by an actor the policy no longer names — refuses ksor-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.

  • 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:agent scaffolds a fresh record, installs it, drops a real two-page
    PDF in src/, and hands claude -p the prompt tutorial 2 hands the reader.
    Once with add-sources present, 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/* and instance.md untouched; the record builds; status: draft, sources present, no id:/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-authoring index.md and 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_KEY in CI (a repository secret the owner has not yet added; the
    tier runs and reports itself skipped until then) or a logged-in claude
    locally, pins a mid-tier model by default (a one-word reply on the default
    model measured $0.25), and runs from skill-evals.yml on 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 under packages/; nothing an adopter installs behaves differently.

  • 87a3542: Correct a claim the tutorial made about build_id, and guard the general rule.

    buildIdOf hashes ksor_version along with the record — deliberately, because
    "what produced this" is part of what a publication is. So a captured build_id
    is correct for exactly one release, and the sentence 0.0.54's tutorial fix added
    — "Yo...

Read more

@panaversity/ksor@0.0.55

Choose a tag to compare

@github-actions github-actions released this 01 Sep 21:35
a83decd

Patch Changes

  • 4ecf549: Test infrastructure only — nothing an adopter installs behaves differently.

    The behavioural evals scored a missing top_cosine as -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. Policy is a reserved type, so the
      record demands sources — ksor build, npm run check and the dev server all
      refused it, and steps 4 through 10 were unreachable. It is now type: 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 said pnpm. On that project pnpm install && pnpm dev fails 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 the trust line it has always printed, the build outputs were
      missing their timestamp, source: and wrote lines, and the port-conflict
      refusal was quoted offering pnpm serve where it says ksor serve.

    The walk also surfaced that ksor init leaves a repo with no commits, so every
    reader's first build prints source: unspecified. Rather than hide it, the
    tutorial now shows it and folds git commit into 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.

  • 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 each failed. 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%), generationReady admitted it and the generation
    flipped — publishing a record in which exactly the passages the owner had
    just edited were unsearchable, ksor ingest exit 0, the billing reason visible
    only in chunks.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). isFatal is optional on EmbeddingProvider,
    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 serve on an
    embedding.provider: openai record said embedding provider "openai" needs an API key and none was supplied and stopped — while ksor serve --help,
    env.example and docs/deploying.md all named GEMINI_API_KEY, which that
    door does not read. The registry row already held keyEnv; 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_KEY is 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-file door avoids it entirely.

    The OpenAI live test announces itself. It is gated on OPENAI_API_KEY, no
    workflow supplied one, and a false describe.runIf contributes 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 prints skipped — 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.yaml into build_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 — displayActor replaces human: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.yaml and
    .ksor/takedowns.yaml are both in build_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 check stays green,
    ksor build emits a byte-identical lock, and the deployed page publishes an
    approver the /md/ twin stamped with that same build_id contradicts. An
    auditor reconciling the page against the lock finds nothing wrong, because the
    string they are auditing was never covered by it.

    Now: people_sha256 joins policy_sha256 and ledger_sha256 in the lock and
    in build_id; .ksor/people.yaml joins the inputs that move source_commit;
    and the site's staleness gate compares it like the other three, so an edit the
    lock never saw refuses with ksor-lock-stale naming the file. Refusing until
    ksor build is 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.ts claimed "duplicate keys are refused by the parser rather than
      resolved by whichever came last". They were not. uniqueKeys: true makes 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.md on 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
    naming people_sha256; run ksor build and commit the lock it writes.

    Found by an adversarial review of this week's commits.

  • b45d477: Say what the scaffold's .mcp.json attaches to an adopter's coding agent, and
    stop the README telling them to destroy it.

    ksor init emits .mcp.json with 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.json with a file containing only test-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.json itself 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...

Read more

@panaversity/ksor@0.0.54

Choose a tag to compare

@github-actions github-actions released this 01 Sep 17:52
3d087ab

Patch Changes

  • 3f55e91: ksor calibrate names the free-tier path when a quota refuses it, and the
    calibration text model moves to gemini-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.md documents 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 one isRetryable'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.md told 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
    reads KSOR_AUDIENCE from the environment once at boot into a per-process
    viewer, and the request path never touches it — docs/authorization.md says so
    plainly ("Any caller holding a valid token gets the whole record") and
    specs/ksor/serve/spec.md names 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 (a retrieval_log row 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 by modelId + column width and never by the vendor. What was
    Gemini-bound was the WIRING: GEMINI_API_KEY was 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 as instance.md names the DSN variable rather than
    hardcoding it.

    provider: openai with model: text-embedding-3-small and dim: 1536 reads
    OPENAI_API_KEY. Over fetch, 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 own index and 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.type and 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
    under embedding_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: buildShippedProvider handed EVERY
    provider Gemini's task labels from global config, so an OpenAI run logged its
    space as text-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, the provenance and governance a 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.md named 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 to packages/ksor/package.json so 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.md said 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 is Services AND vercel.json carries a
    services key, 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, one Services and one Other,
    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 — /mcp answering
    405 is the door refusing a GET, which is how you know it is routed at all.

@panaversity/ksor@0.0.53

Choose a tag to compare

@github-actions github-actions released this 31 Aug 06:52
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.

@panaversity/ksor@0.0.52

Choose a tag to compare

@github-actions github-actions released this 31 Aug 05:55
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.yaml maps 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 — bashiraziz is 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, so human:ciso, human:audit-lead and human: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 into build.lock.json — so a display name
    living there would mean correcting the spelling of somebody's name refused the
    next site build as ksor-lock-stale. Appearing in people.yaml confers 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 --check reports 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 deleting vector_floor — turning the abstention gate off entirely to
    clear the error, which is the escape build/lifecycle-notice.ts refuses 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)