@panaversity/ksor 0.0.54 → 0.0.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +369 -0
- package/dist/checker/check-main.mjs +5 -2
- package/dist/cli.mjs +56 -17
- package/dist/{gateway-api-uhx2l1kC-C2BAxISt.mjs → gateway-api-C0vL3oOK-D24n786A.mjs} +25 -4
- package/dist/gateway.d.mts +2 -2
- package/dist/gateway.mjs +1 -1
- package/docs/deploying.md +7 -1
- package/docs/ingesting.md +10 -5
- package/package.json +1 -1
- package/templates/scaffold/.agents/skills/add-sources/SKILL.md +129 -84
- package/templates/scaffold/.agents/skills/add-sources/verify.mjs +45 -0
- package/templates/scaffold/.agents/skills/format-checker/SKILL.md +18 -46
- package/templates/scaffold/.agents/skills/format-checker/check.mjs +3 -0
- package/templates/scaffold/.agents/skills/intake-interview/SKILL.md +16 -8
- package/templates/scaffold/.claude/skills/add-sources/SKILL.md +129 -84
- package/templates/scaffold/.claude/skills/add-sources/verify.mjs +45 -0
- package/templates/scaffold/.claude/skills/format-checker/SKILL.md +18 -46
- package/templates/scaffold/.claude/skills/format-checker/check.mjs +3 -0
- package/templates/scaffold/.claude/skills/intake-interview/SKILL.md +16 -8
- package/templates/scaffold/AGENTS.md +21 -10
- package/templates/scaffold/README.md +47 -16
- package/templates/scaffold/env.example +6 -1
- package/templates/scaffold/gitignore +4 -3
- package/templates/scaffold/system/site/lib/lock.ts +8 -1
- package/templates/scaffold/system/site/lib/people-rule.ts +56 -0
- package/templates/scaffold/system/site/lib/people.ts +5 -24
- package/templates/scaffold/system/site/lib/stage-knowledge.ts +2 -0
- package/templates/scaffold/system/site/record/load.ts +11 -1
- package/templates/scaffold/system/site/record/lock.ts +12 -0
- package/templates/scaffold/.agents/skills/make-slides/SKILL.md +0 -162
- package/templates/scaffold/.agents/skills/make-summary/SKILL.md +0 -153
- package/templates/scaffold/.claude/skills/make-slides/SKILL.md +0 -162
- package/templates/scaffold/.claude/skills/make-summary/SKILL.md +0 -153
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,374 @@
|
|
|
1
1
|
# @panaversity/ksor
|
|
2
2
|
|
|
3
|
+
## 0.0.56
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- bf35e2f: `add-sources` 2.0.0: a file or a person, one skill — with a check the agent
|
|
8
|
+
runs instead of a rule it is asked to follow.
|
|
9
|
+
|
|
10
|
+
Two issues asked for two paths into the record. #31: an owner with a folder of
|
|
11
|
+
PDFs had no path — the skill stated the rules and converted nothing. #50: an
|
|
12
|
+
owner whose knowledge is only in their head had no path — the interview scoped
|
|
13
|
+
the record and stopped, leaving `knowledge/` full of samples about KSoR.
|
|
14
|
+
|
|
15
|
+
**They are one skill, not two.** The record draws no line between the kinds:
|
|
16
|
+
an interview attestation in `sources[].resource` passes `ksor build` today,
|
|
17
|
+
the fidelity rules read the same for both, and a real owner has BOTH — the
|
|
18
|
+
policy PDF and the exception everyone knows that the PDF never mentions. So
|
|
19
|
+
the person step runs after every file: "what does this not cover?" is the
|
|
20
|
+
question that finds the pages nobody wrote. A sibling skill would have made
|
|
21
|
+
the agent choose before it knew, and #50's own three-way trigger collision
|
|
22
|
+
vanishes.
|
|
23
|
+
|
|
24
|
+
**The source is a file.** Extract first, into a scratch file outside
|
|
25
|
+
`knowledge/` — the skill names the extractor per format (`pdftotext`,
|
|
26
|
+
`pandoc`, macOS `textutil`, `markitdown`) and what to do with none on `PATH`:
|
|
27
|
+
read the file directly and SAY the verification that follows is weaker. An
|
|
28
|
+
empty extraction is a scanned image, and the skill stops and tells the owner
|
|
29
|
+
rather than OCR and hope. Then decide the shape of the record, convert to
|
|
30
|
+
CommonMark a person would have written, name the source precisely, and run
|
|
31
|
+
the shipped check:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
node .agents/skills/add-sources/verify.mjs /tmp/in.txt knowledge/<path>.md
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`verify.mjs` — plain Node, no dependencies, the owner's to keep — lists every
|
|
38
|
+
number, date, threshold, code and capitalised name in the document's body that
|
|
39
|
+
does not appear in the extraction, case-folded and whitespace-collapsed.
|
|
40
|
+
Frontmatter and footnote ids are exempt, because they are the agent's words by
|
|
41
|
+
design. It is a floor, and says so: a value that passes was in the source; it
|
|
42
|
+
cannot see a value that was dropped, and it cannot tell a paraphrase from an
|
|
43
|
+
invention. Model-driven conversion is highest-fidelity for layout and
|
|
44
|
+
lowest for exact values, and this is the mechanical half of "copy load-bearing
|
|
45
|
+
values exactly".
|
|
46
|
+
|
|
47
|
+
**The source is a person.** Ask one question at a time in their words — who
|
|
48
|
+
triggers it, the steps, who approves and at what threshold, what goes wrong,
|
|
49
|
+
the exception — until someone who was not in the room could act on it. Draft
|
|
50
|
+
as the record, not a transcript; anything unconfirmed is an `Open question:`
|
|
51
|
+
line, never prose. The attestation goes in `sources` (who, role, instant,
|
|
52
|
+
conducted by) — there is no `provenance:` key, and no transcript is kept.
|
|
53
|
+
Two people describing one process differently stay two cited statements.
|
|
54
|
+
|
|
55
|
+
**Both end the same way, and the ending is new.** Read it back on `pnpm dev`,
|
|
56
|
+
then ask the owner to approve and write down what they said. A draft reaches
|
|
57
|
+
no machine surface, so a skill that stopped at the draft left every `llms.txt`
|
|
58
|
+
and every door empty — found on the journey walk: `1 document(s), 0 admitted`.
|
|
59
|
+
|
|
60
|
+
The rules that were restated here (placement, frontmatter, audience,
|
|
61
|
+
deprecation) now point at the emitted AGENTS.md instead. The trigger test that
|
|
62
|
+
asserted "no skill claims dictated knowledge" flips: add-sources claims it by
|
|
63
|
+
name, and no other skill may. Eleven cases hold `verify.mjs` to what it does
|
|
64
|
+
and, in three of them, to what it does not claim.
|
|
65
|
+
|
|
66
|
+
- 73530e6: Prune the scaffold's skills to the three that make a record, and fix the two
|
|
67
|
+
seams every adopter hits on the way to one.
|
|
68
|
+
|
|
69
|
+
**Removed: `make-slides` and `make-summary`.** They were 45% of all shipped
|
|
70
|
+
skill text and 21.6% identical to each other — their own commit says
|
|
71
|
+
"make-summary is make-slides' discipline applied to prose". A companion is
|
|
72
|
+
downstream of a record existing and invisible to the agent surface (no route,
|
|
73
|
+
no `llms.txt` line, no MCP node); no fixture and no tutorial ever fired either;
|
|
74
|
+
and neither was ever shown to beat its absence, which is the bar AGENTS.md sets
|
|
75
|
+
for keeping a skill at all. Their one real rule — a card may only say what its
|
|
76
|
+
document says — already lives in the emitted AGENTS.md, and the site renders
|
|
77
|
+
companions exactly as before. Verified: the emitted checker passes with both
|
|
78
|
+
gone. `format-checker/SKILL.md` is cut to what AGENTS.md does not say; the
|
|
79
|
+
program it names is unchanged.
|
|
80
|
+
|
|
81
|
+
**Fixed: `intake-interview` was contradicting itself in its trigger.** The
|
|
82
|
+
always-resident description promised "seven questions"; the body has asked
|
|
83
|
+
three since 2026-08-26. The body handed off to "question 4" and "question 5",
|
|
84
|
+
neither of which exists, and claimed `add-sources` writes `verified:` entries —
|
|
85
|
+
nothing does. 1.6.0 says three, hands off to add-sources with whatever material
|
|
86
|
+
the owner has, and drops the false claim.
|
|
87
|
+
|
|
88
|
+
**Fixed: the recommended path turned a green record red.** Walked on the
|
|
89
|
+
published package:
|
|
90
|
+
|
|
91
|
+
- The README said run the interview, then "delete each starter as your own
|
|
92
|
+
knowledge arrives". Delete the five first and the build refuses
|
|
93
|
+
`ksor-record-empty` and writes nothing — a slug named by no document an
|
|
94
|
+
adopter reads. The README now says to write and approve one document of
|
|
95
|
+
your own before the last starter goes, and names the refusal.
|
|
96
|
+
- The hello-world tutorial approves as `human:you`. The interview then retires
|
|
97
|
+
`human:you` from the policy, and the tutorial's own document — still approved
|
|
98
|
+
by an actor the policy no longer names — refuses `ksor-approver-unauthorised`.
|
|
99
|
+
The interview now re-attributes every act recorded under the placeholder to
|
|
100
|
+
the owner's handle in the same change: it is the same person.
|
|
101
|
+
|
|
102
|
+
**Consolidated:** "the record says only what its source says — a gap is an
|
|
103
|
+
open question, never filled from general knowledge" lived only inside
|
|
104
|
+
`add-sources`; it is now stated once in the emitted AGENTS.md where every
|
|
105
|
+
other writing rule is.
|
|
106
|
+
|
|
107
|
+
Two deterministic gates hold all of this: a skill-consistency lint (a trigger's
|
|
108
|
+
question count matches its body; every "question N" resolves; every refusal
|
|
109
|
+
slug a document names is one the product raises; every skill a document names
|
|
110
|
+
ships) and a journey walk against the built CLI (interview → one draft →
|
|
111
|
+
approve → delete starters → retire the starter actor, plus the exact state
|
|
112
|
+
each refusal fires on). Every lint assertion was mutation-tested.
|
|
113
|
+
|
|
114
|
+
Found by four independent reviews of the plan to build a system of record,
|
|
115
|
+
before building any of it.
|
|
116
|
+
|
|
117
|
+
- ab6a3ed: The agent tier: a shipped skill, run by a real coding agent, with the skill
|
|
118
|
+
and without it — the comparison AGENTS.md has always demanded and nothing had
|
|
119
|
+
ever run (issue #30).
|
|
120
|
+
|
|
121
|
+
`pnpm test:agent` scaffolds a fresh record, installs it, drops a real two-page
|
|
122
|
+
PDF in `src/`, and hands `claude -p` the prompt tutorial 2 hands the reader.
|
|
123
|
+
Once with `add-sources` present, once with it removed. What the agent leaves
|
|
124
|
+
behind is graded, and the split is the Testing contract's own: deterministic
|
|
125
|
+
behavioural graders GATE the with-skill arm — exactly one new document, under
|
|
126
|
+
`finance/`; `.ksor/*` and `instance.md` untouched; the record builds; `status:
|
|
127
|
+
draft`, `sources` present, no `id:`/`name:`; page furniture gone; every
|
|
128
|
+
number, date and name in the body found in the extraction by the shipped
|
|
129
|
+
`verify.mjs`. Cost, turns, duration and the baseline arm are REPORTED, so the
|
|
130
|
+
delta is visible and a skill that stops winning is seen.
|
|
131
|
+
|
|
132
|
+
"Checker passes" is deliberately not a grader: while this was being designed a
|
|
133
|
+
baseline run passed the checker by hand-authoring `index.md` and editing
|
|
134
|
+
`.ksor/people.yaml` — the worse behaviour scoring better. Files touched is the
|
|
135
|
+
discriminating assertion.
|
|
136
|
+
|
|
137
|
+
It spends model tokens, so it is gated like the database tier: on
|
|
138
|
+
`ANTHROPIC_API_KEY` in CI (a repository secret the owner has not yet added; the
|
|
139
|
+
tier runs and reports itself skipped until then) or a logged-in `claude`
|
|
140
|
+
locally, pins a mid-tier model by default (a one-word reply on the default
|
|
141
|
+
model measured $0.25), and runs from `skill-evals.yml` on push to main and by
|
|
142
|
+
hand — never per pull request.
|
|
143
|
+
|
|
144
|
+
What it cannot measure is written in the suite rather than implied: a
|
|
145
|
+
conversational skill needs a scripted owner, "reads as a finished page" needs
|
|
146
|
+
a browser, and the adopter's own model is whatever they run.
|
|
147
|
+
|
|
148
|
+
Decision 31 records the three choices this week made about the skill surface —
|
|
149
|
+
pruned to three, one skill for a file and a person, and this harness shape
|
|
150
|
+
over the Python trigger script that was proposed and measured wanting.
|
|
151
|
+
|
|
152
|
+
- c34cc3a: Tutorial 2, _Make it yours_: the walk from hello world's record to one that is
|
|
153
|
+
only the owner's — every output run and pasted as it appeared.
|
|
154
|
+
|
|
155
|
+
The intake interview and what it does to the placeholder approver; one policy
|
|
156
|
+
brought in from a real PDF, with the shipped check catching the one number the
|
|
157
|
+
conversion got wrong; one procedure that only ever lived in someone's head,
|
|
158
|
+
written with the thing they were not sure of as an open question rather than
|
|
159
|
+
prose; the read-back on the site and the approval act; then the samples go and
|
|
160
|
+
the tool that approved them leaves the policy. Two refusals do work on the way,
|
|
161
|
+
and the tutorial says exactly which state each fires on.
|
|
162
|
+
|
|
163
|
+
The prompt-accounting test now covers both tutorials from one table, so a new
|
|
164
|
+
prompt in either fails until someone names the skill that answers it. Only that
|
|
165
|
+
test changes under `packages/`; nothing an adopter installs behaves differently.
|
|
166
|
+
|
|
167
|
+
- 87a3542: Correct a claim the tutorial made about `build_id`, and guard the general rule.
|
|
168
|
+
|
|
169
|
+
`buildIdOf` hashes `ksor_version` along with the record — deliberately, because
|
|
170
|
+
"what produced this" is part of what a publication is. So a captured `build_id`
|
|
171
|
+
is correct for exactly one release, and the sentence 0.0.54's tutorial fix added
|
|
172
|
+
— "Your timestamp will differ; the `build_id` will not" — was already false when
|
|
173
|
+
0.0.55 published it. A reader on any later ksor sees a hash that does not match
|
|
174
|
+
theirs and nothing saying why.
|
|
175
|
+
|
|
176
|
+
Found by walking the published package rather than by reading the diff: the same
|
|
177
|
+
practice that caught the tutorial being uncompletable caught the correction being
|
|
178
|
+
wrong.
|
|
179
|
+
|
|
180
|
+
The tutorial now says the id carries the toolchain, names the version its
|
|
181
|
+
outputs were captured on, and points at the reproducibility a reader can
|
|
182
|
+
actually check — run `ksor build` twice on one tree and the id is identical.
|
|
183
|
+
Both captured blocks are re-taken from a 0.0.55 walk.
|
|
184
|
+
|
|
185
|
+
A guard in `docs-truth.integration.test.ts` holds the general rule rather than
|
|
186
|
+
the sentence: a document printing a concrete `build_id` must say what moves one,
|
|
187
|
+
within 700 characters of the id. It is PROXIMITY rather than presence — the
|
|
188
|
+
first version asked whether "toolchain" appeared anywhere in the file, the file
|
|
189
|
+
already used the word once for an unrelated reason, and removing the caveat left
|
|
190
|
+
it green. Caught by mutation, and the tightened version immediately found a
|
|
191
|
+
second uncaveated id in the same document.
|
|
192
|
+
|
|
193
|
+
## 0.0.55
|
|
194
|
+
|
|
195
|
+
### Patch Changes
|
|
196
|
+
|
|
197
|
+
- 4ecf549: Test infrastructure only — nothing an adopter installs behaves differently.
|
|
198
|
+
|
|
199
|
+
The behavioural evals scored a missing `top_cosine` as `-1`. When a provider
|
|
200
|
+
rate-limits, the read plane degrades to keyword-only by design, so searches
|
|
201
|
+
answer with no score — and the assertions then compared sentinels, reporting a
|
|
202
|
+
vendor outage as "the abstention floor is broken". Four CI failures in a day
|
|
203
|
+
read that way before anyone looked past the assertion. A missing score now
|
|
204
|
+
refuses, naming the cause, and never invents the number that is absent.
|
|
205
|
+
|
|
206
|
+
- e476197: Fix the hello-world tutorial, which could not be completed as written.
|
|
207
|
+
|
|
208
|
+
Three defects, all found by walking the published 0.0.54 rather than by reading:
|
|
209
|
+
|
|
210
|
+
- Step 3's document declared `type: Policy`. `Policy` is a reserved type, so the
|
|
211
|
+
record demands `sources` — `ksor build`, `npm run check` and the dev server all
|
|
212
|
+
refused it, and steps 4 through 10 were unreachable. It is now `type: Document`,
|
|
213
|
+
the type the profile promises never to reserve, with a note on why and on what
|
|
214
|
+
an agent should do when it reaches for a reserved one.
|
|
215
|
+
- Step 1 scaffolds with `npx`, which emits an **npm** project, and every command
|
|
216
|
+
after it said `pnpm`. On that project `pnpm install && pnpm dev` fails with
|
|
217
|
+
`sh: next: command not found`. All sixteen commands are npm's now, and the step
|
|
218
|
+
that explains manager detection says which one the rest of the tutorial speaks.
|
|
219
|
+
- The captured outputs had been trimmed after capture, in a document whose second
|
|
220
|
+
paragraph promises they were "pasted as it appeared": `ksor serve`'s boot report
|
|
221
|
+
was missing the `trust` line it has always printed, the build outputs were
|
|
222
|
+
missing their timestamp, `source:` and `wrote` lines, and the port-conflict
|
|
223
|
+
refusal was quoted offering `pnpm serve` where it says `ksor serve`.
|
|
224
|
+
|
|
225
|
+
The walk also surfaced that `ksor init` leaves a repo with no commits, so every
|
|
226
|
+
reader's first build prints `source: unspecified`. Rather than hide it, the
|
|
227
|
+
tutorial now shows it and folds `git commit` into the approval step — which is
|
|
228
|
+
where provenance belongs anyway, and which lets the second build print a real
|
|
229
|
+
commit sha.
|
|
230
|
+
|
|
231
|
+
The tutorial also said `.mcp.json`'s "first is Neon's" and named the second
|
|
232
|
+
server nowhere, and said nothing about the Neon server acting on the whole Neon
|
|
233
|
+
account. Both are fixed here for the tutorial; the emitted scaffold's copies of
|
|
234
|
+
the same two defects are fixed separately.
|
|
235
|
+
|
|
236
|
+
Only the tutorial and the test that pins its prompts changed; nothing an adopter
|
|
237
|
+
installs behaves differently.
|
|
238
|
+
|
|
239
|
+
- ae49524: Stop a spent OpenAI balance from quarantining content and flipping a generation,
|
|
240
|
+
and name the right variable when a provider key is missing.
|
|
241
|
+
|
|
242
|
+
**The serious one.** `insufficient_quota` — OpenAI's answer to an exhausted
|
|
243
|
+
balance, which arrives as 429 like an ordinary rate limit — was classified
|
|
244
|
+
non-retryable, correctly, because no amount of waiting adds credit. But
|
|
245
|
+
"non-retryable" is what the ingest drain reads as **poison chunk**: it
|
|
246
|
+
binary-splits the batch down to singletons and marks each `failed`. A spent
|
|
247
|
+
balance arrives on _every_ chunk, so a run walked the queue quarantining
|
|
248
|
+
everything it touched; if the failed fraction stayed under
|
|
249
|
+
`MAX_FAILED_FRACTION` (2%), `generationReady` admitted it and the generation
|
|
250
|
+
**flipped** — publishing a record in which exactly the passages the owner had
|
|
251
|
+
just edited were unsearchable, `ksor ingest` exit 0, the billing reason visible
|
|
252
|
+
only in `chunks.embed_error`. The same event on Gemini aborts the run, so
|
|
253
|
+
switching provider silently changed what a spent quota does.
|
|
254
|
+
|
|
255
|
+
The drain now has three answers instead of two: retryable (abort, chunks stay
|
|
256
|
+
pending), **fatal** (abort the same way, but without spending five backoffs
|
|
257
|
+
first — the account is what is wrong, not the passage), and everything else
|
|
258
|
+
(binary-split to the poison chunk). `isFatal` is optional on `EmbeddingProvider`,
|
|
259
|
+
so a provider that cannot tell keeps the old two-kind behaviour and Gemini's
|
|
260
|
+
path is unchanged.
|
|
261
|
+
|
|
262
|
+
**The missing-key refusal names the variable.** `ksor serve` on an
|
|
263
|
+
`embedding.provider: openai` record said `embedding provider "openai" needs an
|
|
264
|
+
API key and none was supplied` and stopped — while `ksor serve --help`,
|
|
265
|
+
`env.example` and `docs/deploying.md` all named `GEMINI_API_KEY`, which that
|
|
266
|
+
door does not read. The registry row already held `keyEnv`; it now reaches the
|
|
267
|
+
operator (`— set OPENAI_API_KEY`), and all three documents describe the choice
|
|
268
|
+
instead of one vendor.
|
|
269
|
+
|
|
270
|
+
**`ksor calibrate`'s Gemini requirement is stated rather than papered over.**
|
|
271
|
+
Question synthesis is Gemini-only today, so a record embedding with
|
|
272
|
+
`OPENAI_API_KEY` is still refused for a Google key when calibrating through the
|
|
273
|
+
synthesized door. That gap is now said plainly in the refusal and in
|
|
274
|
+
`docs/ingesting.md`, which taught calibration without mentioning it. The
|
|
275
|
+
`--queries-file` door avoids it entirely.
|
|
276
|
+
|
|
277
|
+
**The OpenAI live test announces itself.** It is gated on `OPENAI_API_KEY`, no
|
|
278
|
+
workflow supplied one, and a false `describe.runIf` contributes nothing to a run
|
|
279
|
+
— so the suite its own header calls "the tripwire for vendor drift" was absent
|
|
280
|
+
from CI and reported as absent by nobody. It now prints `skipped — set
|
|
281
|
+
OPENAI_API_KEY`, the way Gemini's does, and CI passes the secret so the tripwire
|
|
282
|
+
arms the moment one is added.
|
|
283
|
+
|
|
284
|
+
Found by an adversarial review of this week's commits.
|
|
285
|
+
|
|
286
|
+
- ff99eb5: Hash `.ksor/people.yaml` into `build_id`, so the two surfaces of one build
|
|
287
|
+
cannot publish different provenance.
|
|
288
|
+
|
|
289
|
+
The phone book added in 0.0.53 rewrites the actor printed on every Owner,
|
|
290
|
+
Approved, Withdrawn and Trust row — `displayActor` replaces `human:contractor-a`
|
|
291
|
+
with "Human: Jane Doe, VP Compliance", and the identifier does not appear on the
|
|
292
|
+
page at all. It was hashed by nothing. `.ksor/governance.yaml` and
|
|
293
|
+
`.ksor/takedowns.yaml` are both in `build_id`; this one was left out, on the
|
|
294
|
+
stated reasoning that including it would refuse the next site build after a
|
|
295
|
+
spelling correction.
|
|
296
|
+
|
|
297
|
+
That is the trade critical rule 1 forbids, and the consequence was reachable
|
|
298
|
+
without doing anything unusual: edit a name, `pnpm check` stays green,
|
|
299
|
+
`ksor build` emits a byte-identical lock, and the deployed page publishes an
|
|
300
|
+
approver the `/md/` twin stamped with that same `build_id` contradicts. An
|
|
301
|
+
auditor reconciling the page against the lock finds nothing wrong, because the
|
|
302
|
+
string they are auditing was never covered by it.
|
|
303
|
+
|
|
304
|
+
Now: `people_sha256` joins `policy_sha256` and `ledger_sha256` in the lock and
|
|
305
|
+
in `build_id`; `.ksor/people.yaml` joins the inputs that move `source_commit`;
|
|
306
|
+
and the site's staleness gate compares it like the other three, so an edit the
|
|
307
|
+
lock never saw refuses with `ksor-lock-stale` naming the file. Refusing until
|
|
308
|
+
`ksor build` is re-run is the behaviour, not a regression — it is what every
|
|
309
|
+
other published byte already does.
|
|
310
|
+
|
|
311
|
+
Two things found alongside it, in the same file:
|
|
312
|
+
|
|
313
|
+
- `people.ts` claimed "duplicate keys are refused by the parser rather than
|
|
314
|
+
resolved by whichever came last". They were not. `uniqueKeys: true` makes the
|
|
315
|
+
parser RECORD a duplicate; `toJS()` still resolves last-wins, and nothing read
|
|
316
|
+
the errors — so two entries for one actor published the second person's name
|
|
317
|
+
on the first person's approval, the precise collision the map replaced a name
|
|
318
|
+
derivation to avoid. A duplicate now drops the whole book, and identifiers are
|
|
319
|
+
published instead.
|
|
320
|
+
- The rule lived behind a module that reads `instance.md` on import, so it could
|
|
321
|
+
only be tested by building a record on disk — which is why it shipped asserted
|
|
322
|
+
by a comment. It is now a leaf, `lib/people-rule.ts`, with the shipped
|
|
323
|
+
function under test.
|
|
324
|
+
|
|
325
|
+
**Upgrading:** a lock written before this refuses with `ksor-lock-invalid`
|
|
326
|
+
naming `people_sha256`; run `ksor build` and commit the lock it writes.
|
|
327
|
+
|
|
328
|
+
Found by an adversarial review of this week's commits.
|
|
329
|
+
|
|
330
|
+
- b45d477: Say what the scaffold's `.mcp.json` attaches to an adopter's coding agent, and
|
|
331
|
+
stop the README telling them to destroy it.
|
|
332
|
+
|
|
333
|
+
`ksor init` emits `.mcp.json` with two servers. The emitted README and AGENTS.md
|
|
334
|
+
both said "the first is Neon" and named the second nowhere — so
|
|
335
|
+
`agentfactory-system-of-record`, a Panaversity-operated endpoint, was wired into
|
|
336
|
+
every adopter's coding agent with no emitted document mentioning it. `.mcp.json`
|
|
337
|
+
attaches servers to the agent that OPERATES the record; a server nobody
|
|
338
|
+
documented is a capability nobody reviewed.
|
|
339
|
+
|
|
340
|
+
Both are now named, with what each is and that either may be deleted. The second
|
|
341
|
+
is described as what it is: a read-only example record that is **not** the
|
|
342
|
+
adopter's and that nothing in the project depends on.
|
|
343
|
+
|
|
344
|
+
The Neon step also said only that the server exists. It acts on the Neon
|
|
345
|
+
_account_ — an agent holding it can create and delete projects and branches — so
|
|
346
|
+
the README and AGENTS.md now say that before handing over a prompt that runs
|
|
347
|
+
against real infrastructure, and point at Neon's own documentation for the
|
|
348
|
+
scopes rather than paraphrasing them.
|
|
349
|
+
|
|
350
|
+
And the "Test the door with an actual agent" section told the adopter to _write_
|
|
351
|
+
`.mcp.json` with a file containing only `test-record` — overwriting the Neon
|
|
352
|
+
entry the same README depends on two sections earlier — and then closed with
|
|
353
|
+
"Delete `.mcp.json`, or keep it". It now shows the entry to **add**, and says not
|
|
354
|
+
to delete the file.
|
|
355
|
+
|
|
356
|
+
A guard derived from `mcp.json` itself asserts every server key appears in both
|
|
357
|
+
emitted documents, so adding a server and saying nothing fails on the server
|
|
358
|
+
that was added. Mutation-tested: unnaming the second server turns both red.
|
|
359
|
+
|
|
360
|
+
Found by an adversarial review of this week's commits. Whether the scaffold
|
|
361
|
+
should ship a second, vendor-operated MCP record at all is an owner question and
|
|
362
|
+
is untouched here.
|
|
363
|
+
|
|
364
|
+
- 5283084: Test infrastructure only — nothing an adopter installs behaves differently.
|
|
365
|
+
|
|
366
|
+
A skill's `description` is its trigger and nothing measured it (#30). Every
|
|
367
|
+
prompt the hello world tells a reader to say is now matched to a shipped skill
|
|
368
|
+
or recorded as needing none, and each skill's trigger phrases are pinned — so
|
|
369
|
+
narrowing one, the failure mode where a skill silently stops firing, goes red
|
|
370
|
+
naming the phrase. The model-scored half of that issue is untouched.
|
|
371
|
+
|
|
3
372
|
## 0.0.54
|
|
4
373
|
|
|
5
374
|
### Patch Changes
|
|
@@ -10892,7 +10892,7 @@ var require_public_api = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
10892
10892
|
exports.stringify = stringify;
|
|
10893
10893
|
}));
|
|
10894
10894
|
//#endregion
|
|
10895
|
-
//#region ../content/dist/check-
|
|
10895
|
+
//#region ../content/dist/check-6fB_kR4G.mjs
|
|
10896
10896
|
var import_dist = (/* @__PURE__ */ __commonJSMin(((exports) => {
|
|
10897
10897
|
var composer = require_composer();
|
|
10898
10898
|
var Document = require_Document();
|
|
@@ -12150,6 +12150,7 @@ function changedFields(before, after) {
|
|
|
12150
12150
|
const CONTROL_FILES = [
|
|
12151
12151
|
"instance.md",
|
|
12152
12152
|
".ksor/governance.yaml",
|
|
12153
|
+
".ksor/people.yaml",
|
|
12153
12154
|
".ksor/takedowns.yaml"
|
|
12154
12155
|
];
|
|
12155
12156
|
/** Files the operating system writes behind the author's back: ignored, never reported. */
|
|
@@ -13714,7 +13715,7 @@ function checkAgainstPolicy(concept, policy, refusals) {
|
|
|
13714
13715
|
}
|
|
13715
13716
|
}
|
|
13716
13717
|
//#endregion
|
|
13717
|
-
//#region ../content/dist/record-
|
|
13718
|
+
//#region ../content/dist/record-Cxw0SUG3.mjs
|
|
13718
13719
|
const hex64 = string().regex(/^[0-9a-f]{64}$/, "a sha256 hex digest");
|
|
13719
13720
|
const viewerList = array(string().min(1));
|
|
13720
13721
|
const lockSchema = object({
|
|
@@ -13732,6 +13733,7 @@ const lockSchema = object({
|
|
|
13732
13733
|
drafts: _enum(["hidden", "shown"]),
|
|
13733
13734
|
instance_sha256: hex64,
|
|
13734
13735
|
policy_sha256: hex64,
|
|
13736
|
+
people_sha256: hex64,
|
|
13735
13737
|
ledger_sha256: hex64,
|
|
13736
13738
|
ledger_entries: array(object({
|
|
13737
13739
|
id: string().min(1),
|
|
@@ -13928,6 +13930,7 @@ const INPUTS = [
|
|
|
13928
13930
|
"knowledge",
|
|
13929
13931
|
"instance.md",
|
|
13930
13932
|
".ksor/governance.yaml",
|
|
13933
|
+
".ksor/people.yaml",
|
|
13931
13934
|
".ksor/takedowns.yaml"
|
|
13932
13935
|
];
|
|
13933
13936
|
function gitFacts(root) {
|
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { n as resolveCommand, r as verbs, t as exitCodes } from "./src-dqpI-p1a.mjs";
|
|
3
|
-
import { A as parseViewer, B as tallyHandlers, C as contentPoolMin, D as outlineHandler, E as keyRingFromEnv, F as recordIsUndescribed, G as z$1, H as validateViewer, I as runProbe$1, L as searchHandler, M as prewarmPool, N as providerKeyEnv$1, O as parseInstanceText$1, P as readHandler, R as servingPolicy$1, S as contentPool$1, T as instancePathOf$1, U as withPgRetry$1, V as tlsPosture, W as withProbeDeadline$1, _ as assertGovernanceServable$1, a as GovernanceGateError$1, b as checkEmbeddingSpace$1, c as McpServer$1, d as READ_ONLY, f as READ_OUTPUT, g as TextSearchConfigMismatch, h as TRUST_TIERS$1, i as FLOOR, j as pooledEndpointFor, k as parseTrustFloor, l as MissingProviderKeyError$1, m as SchemaVersionError, n as ContentStoreError$1, o as MAX_OUTLINE_LIMIT, p as SEARCH_OUTPUT, r as EmbeddingSpaceMismatch$1, t as AudienceError$1, u as OUTLINE_OUTPUT, v as assertSchemaCompatible, w as embedQueryVlit, x as composeInstructions, y as buildShippedProvider$1, z as storedTextSearchConfig } from "./gateway-api-
|
|
3
|
+
import { A as parseViewer, B as tallyHandlers, C as contentPoolMin, D as outlineHandler, E as keyRingFromEnv, F as recordIsUndescribed, G as z$1, H as validateViewer, I as runProbe$1, L as searchHandler, M as prewarmPool, N as providerKeyEnv$1, O as parseInstanceText$1, P as readHandler, R as servingPolicy$1, S as contentPool$1, T as instancePathOf$1, U as withPgRetry$1, V as tlsPosture, W as withProbeDeadline$1, _ as assertGovernanceServable$1, a as GovernanceGateError$1, b as checkEmbeddingSpace$1, c as McpServer$1, d as READ_ONLY, f as READ_OUTPUT, g as TextSearchConfigMismatch, h as TRUST_TIERS$1, i as FLOOR, j as pooledEndpointFor, k as parseTrustFloor, l as MissingProviderKeyError$1, m as SchemaVersionError, n as ContentStoreError$1, o as MAX_OUTLINE_LIMIT, p as SEARCH_OUTPUT, r as EmbeddingSpaceMismatch$1, t as AudienceError$1, u as OUTLINE_OUTPUT, v as assertSchemaCompatible, w as embedQueryVlit, x as composeInstructions, y as buildShippedProvider$1, z as storedTextSearchConfig } from "./gateway-api-C0vL3oOK-D24n786A.mjs";
|
|
4
4
|
import { appendFileSync, chmodSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
6
|
import { InMemoryTransport, LATEST_PROTOCOL_VERSION, createMcpHandler } from "@modelcontextprotocol/server";
|
|
@@ -17,7 +17,7 @@ import { Document, YAMLParseError, isCollection, isMap, isPair, isSeq, parseAllD
|
|
|
17
17
|
import { parseArgs } from "node:util";
|
|
18
18
|
import { readFile } from "node:fs/promises";
|
|
19
19
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
20
|
-
//#region ../content-gateway/dist/main-
|
|
20
|
+
//#region ../content-gateway/dist/main-BoKhKcP0.mjs
|
|
21
21
|
/**
|
|
22
22
|
* The default registration — and the ORIGINAL of the file `ksor init` emits.
|
|
23
23
|
*
|
|
@@ -1533,7 +1533,7 @@ async function main$1(version = GATEWAY_VERSION) {
|
|
|
1533
1533
|
}
|
|
1534
1534
|
}
|
|
1535
1535
|
//#endregion
|
|
1536
|
-
//#region ../content/dist/check-
|
|
1536
|
+
//#region ../content/dist/check-6fB_kR4G.mjs
|
|
1537
1537
|
/**
|
|
1538
1538
|
* One reader for the control files beside the bundle (`.ksor/*.yaml`): the
|
|
1539
1539
|
* same parser posture as a concept's frontmatter — one document, unique keys,
|
|
@@ -2825,6 +2825,7 @@ const LEDGER_HEADER$1 = "# The takedown ledger (record spec §5): append-only, w
|
|
|
2825
2825
|
const CONTROL_FILES = [
|
|
2826
2826
|
"instance.md",
|
|
2827
2827
|
".ksor/governance.yaml",
|
|
2828
|
+
".ksor/people.yaml",
|
|
2828
2829
|
".ksor/takedowns.yaml"
|
|
2829
2830
|
];
|
|
2830
2831
|
/** Files the operating system writes behind the author's back: ignored, never reported. */
|
|
@@ -4709,7 +4710,7 @@ async function withPgRetry(op, options = {}) {
|
|
|
4709
4710
|
throw lastError;
|
|
4710
4711
|
}
|
|
4711
4712
|
//#endregion
|
|
4712
|
-
//#region ../content/dist/commands-
|
|
4713
|
+
//#region ../content/dist/commands-CO9-bQnb.mjs
|
|
4713
4714
|
/**
|
|
4714
4715
|
* EVAL-LOCKED constants, quarried verbatim from the oracle
|
|
4715
4716
|
* (sor-agentfactory @ b554f91, config.py) — changing any of these is a
|
|
@@ -6228,6 +6229,14 @@ function isRetryable(exc) {
|
|
|
6228
6229
|
* project stays rate-limited on the next second, so a search degrades to
|
|
6229
6230
|
* keyword-only now rather than stalling a reader behind backoff.
|
|
6230
6231
|
*/
|
|
6232
|
+
/**
|
|
6233
|
+
* An ACCOUNT-level failure: no amount of waiting and no other passage changes
|
|
6234
|
+
* it. The drain must abort on this rather than quarantine, because the chunk
|
|
6235
|
+
* it happened to be holding is not what is wrong — see `ingest/worker.ts`.
|
|
6236
|
+
*/
|
|
6237
|
+
function isFatal(exc) {
|
|
6238
|
+
return exc instanceof OpenAiHttpError && exc.kind === "insufficient_quota";
|
|
6239
|
+
}
|
|
6231
6240
|
function isRetryableQuery(exc) {
|
|
6232
6241
|
if (isTransportBlip(exc)) return true;
|
|
6233
6242
|
const status = httpStatusOf(exc);
|
|
@@ -6278,6 +6287,9 @@ var OpenAiEmbeddingProvider = class {
|
|
|
6278
6287
|
isRetryableQuery(exc) {
|
|
6279
6288
|
return isRetryableQuery(exc);
|
|
6280
6289
|
}
|
|
6290
|
+
isFatal(exc) {
|
|
6291
|
+
return isFatal(exc);
|
|
6292
|
+
}
|
|
6281
6293
|
};
|
|
6282
6294
|
/**
|
|
6283
6295
|
* The embedding-provider registry — a plain object, NOT any discovery
|
|
@@ -6304,10 +6316,20 @@ var OpenAiEmbeddingProvider = class {
|
|
|
6304
6316
|
*/
|
|
6305
6317
|
var MissingProviderKeyError = class extends Error {
|
|
6306
6318
|
providerName;
|
|
6307
|
-
|
|
6308
|
-
|
|
6319
|
+
keyEnv;
|
|
6320
|
+
/**
|
|
6321
|
+
* `keyEnv` is not decoration. The message named the PROVIDER and nothing
|
|
6322
|
+
* else, so an operator whose `ksor serve` exited 3 on an OpenAI record was
|
|
6323
|
+
* told "provider openai needs an API key" and left to guess which variable —
|
|
6324
|
+
* while `ksor serve --help`, `env.example` and `docs/deploying.md` all named
|
|
6325
|
+
* `GEMINI_API_KEY`, which the door does not read (review, 2026-09-01). The
|
|
6326
|
+
* registry row already held the answer; this is it reaching the operator.
|
|
6327
|
+
*/
|
|
6328
|
+
constructor(providerName, keyEnv = null) {
|
|
6329
|
+
super(`embedding provider ${JSON.stringify(providerName)} needs an API key and none was supplied` + (keyEnv === null ? "" : ` — set ${keyEnv}`));
|
|
6309
6330
|
this.name = "MissingProviderKeyError";
|
|
6310
6331
|
this.providerName = providerName;
|
|
6332
|
+
this.keyEnv = keyEnv;
|
|
6311
6333
|
}
|
|
6312
6334
|
};
|
|
6313
6335
|
const PROVIDERS = {
|
|
@@ -6369,7 +6391,7 @@ function providerKeyEnv(name) {
|
|
|
6369
6391
|
*/
|
|
6370
6392
|
function buildShippedProvider(name, opts) {
|
|
6371
6393
|
const entry = entryFor(name);
|
|
6372
|
-
if (entry.needsApiKey && !opts.apiKey) throw new MissingProviderKeyError(name);
|
|
6394
|
+
if (entry.needsApiKey && !opts.apiKey) throw new MissingProviderKeyError(name, entry.keyEnv);
|
|
6373
6395
|
return entry.build({
|
|
6374
6396
|
apiKey: opts.apiKey ?? "",
|
|
6375
6397
|
modelId: opts.modelId ?? "gemini-embedding-001",
|
|
@@ -9275,6 +9297,7 @@ async function drain(pending, io) {
|
|
|
9275
9297
|
literals = await io.embedBatch(batch.map(([, text]) => text));
|
|
9276
9298
|
} catch (exc) {
|
|
9277
9299
|
if (io.isRetryable(exc)) throw exc;
|
|
9300
|
+
if (io.isFatal?.(exc) === true) throw exc;
|
|
9278
9301
|
if (batch.length === 1) {
|
|
9279
9302
|
await io.markFailed(failureReason(exc), batch[0][0]);
|
|
9280
9303
|
failed += 1;
|
|
@@ -9826,7 +9849,8 @@ async function buildGeneration(pool, instance, options) {
|
|
|
9826
9849
|
markFailed: (reason, chunkId) => runIngest(pool, tenant, async (c) => {
|
|
9827
9850
|
await c.query(FAIL_SQL, [reason, chunkId]);
|
|
9828
9851
|
}),
|
|
9829
|
-
isRetryable: (exc) => provider.isRetryable(exc)
|
|
9852
|
+
isRetryable: (exc) => provider.isRetryable(exc),
|
|
9853
|
+
isFatal: (exc) => provider.isFatal?.(exc) === true
|
|
9830
9854
|
});
|
|
9831
9855
|
log(`embedded ${embedded}, failed ${failed}`);
|
|
9832
9856
|
const fin = await runIngest(pool, tenant, async (c) => {
|
|
@@ -10633,7 +10657,7 @@ async function calibrateCommand(args) {
|
|
|
10633
10657
|
let textGenerator = null;
|
|
10634
10658
|
if (queries === null) {
|
|
10635
10659
|
const apiKey = process.env["GEMINI_API_KEY"];
|
|
10636
|
-
if (apiKey === void 0 || apiKey === "") return refuse$2("bad-args", "the synthesized door needs GEMINI_API_KEY (it writes one probe question per sampled passage) — or calibrate with zero LLM: --queries-file PATH (one in-corpus question per line)");
|
|
10660
|
+
if (apiKey === void 0 || apiKey === "") return refuse$2("bad-args", "the synthesized door needs GEMINI_API_KEY (it writes one probe question per sampled passage) — or calibrate with zero LLM: --queries-file PATH (one in-corpus question per line).\n note: this is the TEXT generator, not the embedding provider. A record on `embedding.provider: openai` still embeds with OPENAI_API_KEY; only question synthesis is Gemini-only today, and --queries-file avoids it entirely");
|
|
10637
10661
|
textGenerator = new GeminiTextGenerator({ apiKey });
|
|
10638
10662
|
}
|
|
10639
10663
|
const ooc = values["ooc-file"] === void 0 ? null : parseQueriesFile(readFileSync(values["ooc-file"], "utf8"));
|
|
@@ -11015,7 +11039,7 @@ async function runContentCli(argv) {
|
|
|
11015
11039
|
}
|
|
11016
11040
|
}
|
|
11017
11041
|
//#endregion
|
|
11018
|
-
//#region ../content/dist/record-
|
|
11042
|
+
//#region ../content/dist/record-Cxw0SUG3.mjs
|
|
11019
11043
|
/**
|
|
11020
11044
|
* May `surface` publish `doc` at instant `at`? `drafts` is the build's drafts switch,
|
|
11021
11045
|
* which admits drafts to HUMAN surfaces only.
|
|
@@ -11061,6 +11085,7 @@ const lockSchema = z.object({
|
|
|
11061
11085
|
drafts: z.enum(["hidden", "shown"]),
|
|
11062
11086
|
instance_sha256: hex64,
|
|
11063
11087
|
policy_sha256: hex64,
|
|
11088
|
+
people_sha256: hex64,
|
|
11064
11089
|
ledger_sha256: hex64,
|
|
11065
11090
|
ledger_entries: z.array(z.object({
|
|
11066
11091
|
id: z.string().min(1),
|
|
@@ -11133,6 +11158,7 @@ function buildIdOf(inputs) {
|
|
|
11133
11158
|
indexes: [...inputs.indexes].map((i) => [i.path, i.sha256]).sort((a, b) => compare(a[0] ?? "", b[0] ?? "")),
|
|
11134
11159
|
instance_sha256: inputs.instance_sha256,
|
|
11135
11160
|
policy_sha256: inputs.policy_sha256,
|
|
11161
|
+
people_sha256: inputs.people_sha256,
|
|
11136
11162
|
ledger_sha256: inputs.ledger_sha256,
|
|
11137
11163
|
ksor_version: inputs.ksor_version,
|
|
11138
11164
|
drafts: inputs.drafts
|
|
@@ -11179,6 +11205,7 @@ function composeLock(input) {
|
|
|
11179
11205
|
}));
|
|
11180
11206
|
const instance_sha256 = sha256Hex(input.instanceText);
|
|
11181
11207
|
const policy_sha256 = sha256Hex(input.policyText);
|
|
11208
|
+
const people_sha256 = sha256Hex(input.peopleText ?? "");
|
|
11182
11209
|
const ledger_sha256 = sha256Hex(input.ledgerText ?? "");
|
|
11183
11210
|
return {
|
|
11184
11211
|
format: 1,
|
|
@@ -11189,6 +11216,7 @@ function composeLock(input) {
|
|
|
11189
11216
|
indexes,
|
|
11190
11217
|
instance_sha256,
|
|
11191
11218
|
policy_sha256,
|
|
11219
|
+
people_sha256,
|
|
11192
11220
|
ledger_sha256,
|
|
11193
11221
|
ksor_version: input.ksorVersion,
|
|
11194
11222
|
drafts: input.drafts
|
|
@@ -11201,6 +11229,7 @@ function composeLock(input) {
|
|
|
11201
11229
|
drafts: input.drafts,
|
|
11202
11230
|
instance_sha256,
|
|
11203
11231
|
policy_sha256,
|
|
11232
|
+
people_sha256,
|
|
11204
11233
|
ledger_sha256,
|
|
11205
11234
|
ledger_entries: [...input.ledgerEntries].sort((a, b) => compare(a.id, b.id)),
|
|
11206
11235
|
audiences: {
|
|
@@ -11363,6 +11392,7 @@ const INPUTS = [
|
|
|
11363
11392
|
"knowledge",
|
|
11364
11393
|
"instance.md",
|
|
11365
11394
|
".ksor/governance.yaml",
|
|
11395
|
+
".ksor/people.yaml",
|
|
11366
11396
|
".ksor/takedowns.yaml"
|
|
11367
11397
|
];
|
|
11368
11398
|
/**
|
|
@@ -11373,7 +11403,11 @@ const INPUTS = [
|
|
|
11373
11403
|
* clone is invisible to every surface, so the build says so by name.
|
|
11374
11404
|
*/
|
|
11375
11405
|
function ignoredGovernance(root) {
|
|
11376
|
-
const paths = [
|
|
11406
|
+
const paths = [
|
|
11407
|
+
".ksor/governance.yaml",
|
|
11408
|
+
".ksor/people.yaml",
|
|
11409
|
+
".ksor/takedowns.yaml"
|
|
11410
|
+
].filter((rel) => existsSync(join(root, rel)));
|
|
11377
11411
|
if (paths.length === 0) return [];
|
|
11378
11412
|
const out = git(root, [
|
|
11379
11413
|
"check-ignore",
|
|
@@ -11563,8 +11597,8 @@ function runBuild(args, cwd, io, options) {
|
|
|
11563
11597
|
const facts = gitFacts(root);
|
|
11564
11598
|
if (facts.repository && facts.historicLedger === null && !parsed.allowUnverifiable) return refuse$1(io, "ksor-ledger-unverifiable", facts.historyUnreadable === "shallow" ? "this is a shallow clone: the takedown ledger is append-only, and without history a deleted entry cannot be told from one that never existed" : "git could not read the takedown ledger's history (`git log -- .ksor/takedowns.yaml` failed, and this is not a shallow clone): the ledger is append-only, and without history a deleted entry cannot be told from one that never existed", facts.historyUnreadable === "shallow" ? "fetch full history (`git fetch --unshallow`; in CI, `fetch-depth: 0`), or pass --allow-unverifiable-ledger to build anyway" : "check that `git log` works in this checkout, or pass --allow-unverifiable-ledger to build anyway");
|
|
11565
11599
|
const ignored = facts.repository ? ignoredGovernance(root) : [];
|
|
11566
|
-
if (ignored.length > 0) return refuse$1(io, "ksor-governance-ignored", `git ignores ${ignored.join(" and ")}, so ${ignored.length === 1 ? "it is" : "they are"} in no commit — the policy and the takedown ledger ARE the record, and a clone (your CI, your deploy) would build without ${ignored.length === 1 ? "it" : "them"}`, "un-ignore them in .gitignore — the directory form `.ksor/` cannot be negated, so use `.ksor/*` plus `!.ksor/governance.yaml` and `!.ksor/takedowns.yaml` — then commit them (`ksor migrate` offers that edit)");
|
|
11567
|
-
if (parsed.strict && facts.dirty) return refuse$1(io, "ksor-build-dirty", facts.repository ? "an input (knowledge/, instance.md, .ksor/governance.yaml, .ksor/takedowns.yaml) differs from its last commit, and --strict stamps only committed content" : "the record is not in a git repository, so no input is committed", "commit the inputs and rebuild, or drop --strict to stamp a dirty build (the lock says `dirty: true`)");
|
|
11600
|
+
if (ignored.length > 0) return refuse$1(io, "ksor-governance-ignored", `git ignores ${ignored.join(" and ")}, so ${ignored.length === 1 ? "it is" : "they are"} in no commit — the policy and the takedown ledger ARE the record, and a clone (your CI, your deploy) would build without ${ignored.length === 1 ? "it" : "them"}`, "un-ignore them in .gitignore — the directory form `.ksor/` cannot be negated, so use `.ksor/*` plus `!.ksor/governance.yaml`, `!.ksor/people.yaml` and `!.ksor/takedowns.yaml` — then commit them (`ksor migrate` offers that edit)");
|
|
11601
|
+
if (parsed.strict && facts.dirty) return refuse$1(io, "ksor-build-dirty", facts.repository ? "an input (knowledge/, instance.md, .ksor/governance.yaml, .ksor/people.yaml, .ksor/takedowns.yaml) differs from its last commit, and --strict stamps only committed content" : "the record is not in a git repository, so no input is committed", "commit the inputs and rebuild, or drop --strict to stamp a dirty build (the lock says `dirty: true`)");
|
|
11568
11602
|
const baselines = [];
|
|
11569
11603
|
if (facts.historicLedger !== null) baselines.push({
|
|
11570
11604
|
source: "git history",
|
|
@@ -11605,6 +11639,7 @@ function runBuild(args, cwd, io, options) {
|
|
|
11605
11639
|
drafts: options.drafts,
|
|
11606
11640
|
instanceText: record.files.get("instance.md") ?? "",
|
|
11607
11641
|
policyText: record.files.get(".ksor/governance.yaml") ?? "",
|
|
11642
|
+
peopleText: record.files.get(".ksor/people.yaml") ?? null,
|
|
11608
11643
|
ledgerText,
|
|
11609
11644
|
ledgerEntries: result.ledgerEntries,
|
|
11610
11645
|
audiences: result.policy?.audiences ?? [],
|
|
@@ -13291,11 +13326,13 @@ const BARE_DOTKSOR_PATTERNS = /* @__PURE__ */ new Set([
|
|
|
13291
13326
|
]);
|
|
13292
13327
|
const GOVERNANCE_IGNORE_BLOCK = [
|
|
13293
13328
|
"# ksor's working directory — build output and scratch, never the record.",
|
|
13294
|
-
"# The
|
|
13295
|
-
"#
|
|
13296
|
-
"# cannot be negated, so the
|
|
13329
|
+
"# The governance files inside it ARE the record (the policy, the takedown",
|
|
13330
|
+
"# ledger, and the phone book the site publishes names from) and are",
|
|
13331
|
+
"# un-ignored by name: the directory form `.ksor/` cannot be negated, so the",
|
|
13332
|
+
"# glob is `.ksor/*`.",
|
|
13297
13333
|
".ksor/*",
|
|
13298
13334
|
"!.ksor/governance.yaml",
|
|
13335
|
+
"!.ksor/people.yaml",
|
|
13299
13336
|
"!.ksor/takedowns.yaml"
|
|
13300
13337
|
];
|
|
13301
13338
|
/**
|
|
@@ -13932,7 +13969,9 @@ serves nothing. Runs in this process and holds it; SIGTERM/SIGINT drains.
|
|
|
13932
13969
|
Configured by environment — .env beside the record is read automatically:
|
|
13933
13970
|
|
|
13934
13971
|
<database.dsn_env> the Postgres DSN, under the NAME instance.md gives
|
|
13935
|
-
|
|
13972
|
+
<provider key> iff the instance's embedding provider needs one:
|
|
13973
|
+
GEMINI_API_KEY for gemini, OPENAI_API_KEY for openai.
|
|
13974
|
+
The refusal names the variable your record needs
|
|
13936
13975
|
KSOR_AUTH disabled-local (loopback dev) | disabled-public.
|
|
13937
13976
|
Serve REFUSES to boot with neither this nor a
|
|
13938
13977
|
configured SSO door — never open by accident
|