@panaversity/ksor 0.0.58 → 0.0.59
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 +167 -0
- package/README.md +8 -5
- package/dist/checker/check-main.mjs +142 -136
- package/dist/cli.mjs +810 -211
- package/dist/{gateway-api-DvM2mVtD-BXmUS4T8.mjs → gateway-api-CEsb-e8z-Ch6oMaq2.mjs} +74 -7
- package/dist/gateway.d.mts +2 -2
- package/dist/gateway.mjs +1 -1
- package/docs/building.md +157 -0
- package/docs/deploying.md +14 -2
- package/docs/index.md +5 -1
- package/docs/ingesting.md +29 -2
- package/docs/upgrading.md +15 -1
- package/package.json +3 -3
- package/templates/scaffold/.agents/skills/format-checker/check.mjs +140 -134
- package/templates/scaffold/.claude/skills/format-checker/check.mjs +140 -134
- package/templates/scaffold/AGENTS.md +21 -5
- package/templates/scaffold/README.md +4 -1
- package/templates/scaffold/env.example +3 -0
- package/templates/scaffold/system/site/next-no-telemetry.mjs +27 -0
- package/templates/scaffold/system/site/package.json +2 -2
- package/templates/scaffold/system/site/record/change-control.ts +319 -0
- package/templates/scaffold/system/site/record/index.ts +8 -0
- package/templates/scaffold/system/site/record/lock.ts +37 -0
- package/templates/scaffold/system/site/record/refusal.ts +1 -0
|
@@ -12672,6 +12672,139 @@ function nearMissOf(baseName) {
|
|
|
12672
12672
|
return null;
|
|
12673
12673
|
}
|
|
12674
12674
|
/**
|
|
12675
|
+
* The takedown ledger's OTHER baseline: every entry any COMMITTED version of
|
|
12676
|
+
* `.ksor/takedowns.yaml` has ever carried.
|
|
12677
|
+
*
|
|
12678
|
+
* The committed lock is a baseline too, and a good one — it holds each entry's
|
|
12679
|
+
* digest, so an entry retargeted in place is caught. What it cannot do is prove
|
|
12680
|
+
* that an entry was never deleted, because the lock travels in the SAME change
|
|
12681
|
+
* as the ledger: delete the row, recompute `ledger_sha256`, empty
|
|
12682
|
+
* `ledger_entries`, and the two agree with each other about a denial that is
|
|
12683
|
+
* gone. Only history remembers.
|
|
12684
|
+
*
|
|
12685
|
+
* The baseline may be INCOMPLETE only if it SAYS so: every version history
|
|
12686
|
+
* holds is read, or `entries` comes back null and the caller reports that it
|
|
12687
|
+
* could not verify. A version silently skipped would contribute neither
|
|
12688
|
+
* digests nor ids while the answer still read "verified"
|
|
12689
|
+
* (`git-ledger.integration.test.ts`).
|
|
12690
|
+
*
|
|
12691
|
+
* This lives in the record module because THREE surfaces need the same answer —
|
|
12692
|
+
* `ksor build`, the emitted checker, and the site's stage (decision 19: a
|
|
12693
|
+
* surface that refuses must refuse on both surfaces). Plain `git log` / `git
|
|
12694
|
+
* show`, so nothing here needs installing.
|
|
12695
|
+
*/
|
|
12696
|
+
const LEDGER = ".ksor/takedowns.yaml";
|
|
12697
|
+
/**
|
|
12698
|
+
* `spawnSync` defaults to a 1 MB stdout buffer, and past it the child is KILLED
|
|
12699
|
+
* — `status` comes back null, so the query reads as a failure. A ledger with a
|
|
12700
|
+
* few thousand entries, or one entry carrying a long reason, clears 1 MB
|
|
12701
|
+
* easily, and the version was then dropped from the baseline while the caller
|
|
12702
|
+
* was still told history had been verified. The ceiling stays finite on
|
|
12703
|
+
* purpose: past it this returns null, which is a state the caller SAYS.
|
|
12704
|
+
*/
|
|
12705
|
+
const MAX_BUFFER$1 = 67108864;
|
|
12706
|
+
/** One git query, read-only. Null on any non-zero exit, including no git at all. */
|
|
12707
|
+
function git(root, args) {
|
|
12708
|
+
const r = spawnSync("git", [...args], {
|
|
12709
|
+
cwd: root,
|
|
12710
|
+
encoding: "utf8",
|
|
12711
|
+
maxBuffer: MAX_BUFFER$1
|
|
12712
|
+
});
|
|
12713
|
+
return r.status === 0 ? r.stdout : null;
|
|
12714
|
+
}
|
|
12715
|
+
function historicLedger(root) {
|
|
12716
|
+
const inside = git(root, ["rev-parse", "--is-inside-work-tree"]);
|
|
12717
|
+
if (inside === null || inside.trim() !== "true") return {
|
|
12718
|
+
repository: false,
|
|
12719
|
+
shallow: false,
|
|
12720
|
+
entries: null,
|
|
12721
|
+
unreadable: null
|
|
12722
|
+
};
|
|
12723
|
+
const shallow = (git(root, ["rev-parse", "--is-shallow-repository"]) ?? "").trim() === "true";
|
|
12724
|
+
const born = git(root, [
|
|
12725
|
+
"rev-parse",
|
|
12726
|
+
"--verify",
|
|
12727
|
+
"--quiet",
|
|
12728
|
+
"HEAD"
|
|
12729
|
+
]) !== null;
|
|
12730
|
+
const entries = shallow ? null : born ? historicEntries(root) : [];
|
|
12731
|
+
return {
|
|
12732
|
+
repository: true,
|
|
12733
|
+
shallow,
|
|
12734
|
+
entries,
|
|
12735
|
+
unreadable: entries !== null ? null : shallow ? "shallow" : "unreadable"
|
|
12736
|
+
};
|
|
12737
|
+
}
|
|
12738
|
+
/**
|
|
12739
|
+
* Every id history has ever recorded, each with the text it carried the FIRST
|
|
12740
|
+
* time it was written. A version that parses contributes each entry's digest,
|
|
12741
|
+
* so an entry EDITED in place is caught, not only one deleted; a version that
|
|
12742
|
+
* no longer parses still contributes its ids, read permissively — the point
|
|
12743
|
+
* there is that an id once written never disappears.
|
|
12744
|
+
*
|
|
12745
|
+
* FIRST, not every: keying this by `id\tdigest` kept one baseline entry per
|
|
12746
|
+
* version an id ever had, so a tamper that was COMMITTED and then UNDONE left
|
|
12747
|
+
* two digests for one id, the restored entry matched only one of them, and
|
|
12748
|
+
* `ksor-ledger-amended` fired for good. The record became permanently
|
|
12749
|
+
* unbuildable — by a tamper that had already been put right — and the only
|
|
12750
|
+
* escape was rewriting git history, which is not a remedy a refusal may
|
|
12751
|
+
* demand (found in review, 2026-08-25).
|
|
12752
|
+
*
|
|
12753
|
+
* Taking the OLDEST is what makes the guarantee both enforceable and
|
|
12754
|
+
* escapable. It still refuses a committed tamper (the baseline is what the
|
|
12755
|
+
* entry said when it was written, so committing the edit does not launder
|
|
12756
|
+
* it), and the remedy it names — put the entry back — now actually clears
|
|
12757
|
+
* it. Taking the NEWEST would have done the opposite on both counts.
|
|
12758
|
+
*/
|
|
12759
|
+
function historicEntries(root) {
|
|
12760
|
+
const atRoot = `${(git(root, ["rev-parse", "--show-prefix"]) ?? "").trim()}${LEDGER}`;
|
|
12761
|
+
const commits = git(root, [
|
|
12762
|
+
"log",
|
|
12763
|
+
"--full-history",
|
|
12764
|
+
"--topo-order",
|
|
12765
|
+
"--format=%H",
|
|
12766
|
+
"--",
|
|
12767
|
+
LEDGER
|
|
12768
|
+
]);
|
|
12769
|
+
if (commits === null) return null;
|
|
12770
|
+
const seen = /* @__PURE__ */ new Map();
|
|
12771
|
+
for (const sha of commits.split("\n").filter((s) => s !== "")) {
|
|
12772
|
+
const text = git(root, ["show", `${sha}:${atRoot}`]);
|
|
12773
|
+
if (text === null) {
|
|
12774
|
+
const listed = git(root, [
|
|
12775
|
+
"ls-tree",
|
|
12776
|
+
"--full-tree",
|
|
12777
|
+
"--name-only",
|
|
12778
|
+
sha,
|
|
12779
|
+
"--",
|
|
12780
|
+
atRoot
|
|
12781
|
+
]);
|
|
12782
|
+
if (listed !== null && listed.trim() === "") continue;
|
|
12783
|
+
return null;
|
|
12784
|
+
}
|
|
12785
|
+
const where = sha.slice(0, 7);
|
|
12786
|
+
const parsed = parseLedger(text, LEDGER);
|
|
12787
|
+
if (parsed.ok) {
|
|
12788
|
+
for (const entry of parsed.ledger.entries) seen.set(entry.id, {
|
|
12789
|
+
id: entry.id,
|
|
12790
|
+
digest: entryDigest(entry),
|
|
12791
|
+
entry,
|
|
12792
|
+
where
|
|
12793
|
+
});
|
|
12794
|
+
continue;
|
|
12795
|
+
}
|
|
12796
|
+
for (const m of text.matchAll(/^\s*(?:-\s+)?id:\s*["']?([^\s"']+)/gm)) {
|
|
12797
|
+
const id = m[1] ?? "";
|
|
12798
|
+
if (!seen.has(id)) seen.set(id, {
|
|
12799
|
+
id,
|
|
12800
|
+
digest: null,
|
|
12801
|
+
where
|
|
12802
|
+
});
|
|
12803
|
+
}
|
|
12804
|
+
}
|
|
12805
|
+
return [...seen.values()].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
12806
|
+
}
|
|
12807
|
+
/**
|
|
12675
12808
|
* The widening rule: a link, a `ksor.superseded_by` pointer or a companion
|
|
12676
12809
|
* body may reach a target whose audience contains `public` or contains every
|
|
12677
12810
|
* identifier in the source's — then every reader of the source can read the
|
|
@@ -13537,6 +13670,11 @@ function checkAgainstPolicy(concept, policy, refusals) {
|
|
|
13537
13670
|
}
|
|
13538
13671
|
const hex64 = string().regex(/^[0-9a-f]{64}$/, "a sha256 hex digest");
|
|
13539
13672
|
const viewerList = array(string().min(1));
|
|
13673
|
+
const bundleEntry = object({
|
|
13674
|
+
viewer: string().min(1),
|
|
13675
|
+
sha256: hex64,
|
|
13676
|
+
files: number().int().nonnegative()
|
|
13677
|
+
}).strict();
|
|
13540
13678
|
const lockSchema = object({
|
|
13541
13679
|
format: literal(1),
|
|
13542
13680
|
build_id: string().regex(/^sha256:[0-9a-f]{64}$/),
|
|
@@ -13584,7 +13722,8 @@ const lockSchema = object({
|
|
|
13584
13722
|
indexes: array(object({
|
|
13585
13723
|
path: string().min(1),
|
|
13586
13724
|
sha256: hex64
|
|
13587
|
-
}).strict())
|
|
13725
|
+
}).strict()),
|
|
13726
|
+
bundles: array(bundleEntry)
|
|
13588
13727
|
}).strict();
|
|
13589
13728
|
function parseLock(text) {
|
|
13590
13729
|
let value;
|
|
@@ -13609,139 +13748,6 @@ function parseLock(text) {
|
|
|
13609
13748
|
lock: parsed.data
|
|
13610
13749
|
};
|
|
13611
13750
|
}
|
|
13612
|
-
/**
|
|
13613
|
-
* The takedown ledger's OTHER baseline: every entry any COMMITTED version of
|
|
13614
|
-
* `.ksor/takedowns.yaml` has ever carried.
|
|
13615
|
-
*
|
|
13616
|
-
* The committed lock is a baseline too, and a good one — it holds each entry's
|
|
13617
|
-
* digest, so an entry retargeted in place is caught. What it cannot do is prove
|
|
13618
|
-
* that an entry was never deleted, because the lock travels in the SAME change
|
|
13619
|
-
* as the ledger: delete the row, recompute `ledger_sha256`, empty
|
|
13620
|
-
* `ledger_entries`, and the two agree with each other about a denial that is
|
|
13621
|
-
* gone. Only history remembers.
|
|
13622
|
-
*
|
|
13623
|
-
* The baseline may be INCOMPLETE only if it SAYS so: every version history
|
|
13624
|
-
* holds is read, or `entries` comes back null and the caller reports that it
|
|
13625
|
-
* could not verify. A version silently skipped would contribute neither
|
|
13626
|
-
* digests nor ids while the answer still read "verified"
|
|
13627
|
-
* (`git-ledger.integration.test.ts`).
|
|
13628
|
-
*
|
|
13629
|
-
* This lives in the record module because THREE surfaces need the same answer —
|
|
13630
|
-
* `ksor build`, the emitted checker, and the site's stage (decision 19: a
|
|
13631
|
-
* surface that refuses must refuse on both surfaces). Plain `git log` / `git
|
|
13632
|
-
* show`, so nothing here needs installing.
|
|
13633
|
-
*/
|
|
13634
|
-
const LEDGER = ".ksor/takedowns.yaml";
|
|
13635
|
-
/**
|
|
13636
|
-
* `spawnSync` defaults to a 1 MB stdout buffer, and past it the child is KILLED
|
|
13637
|
-
* — `status` comes back null, so the query reads as a failure. A ledger with a
|
|
13638
|
-
* few thousand entries, or one entry carrying a long reason, clears 1 MB
|
|
13639
|
-
* easily, and the version was then dropped from the baseline while the caller
|
|
13640
|
-
* was still told history had been verified. The ceiling stays finite on
|
|
13641
|
-
* purpose: past it this returns null, which is a state the caller SAYS.
|
|
13642
|
-
*/
|
|
13643
|
-
const MAX_BUFFER = 67108864;
|
|
13644
|
-
/** One git query, read-only. Null on any non-zero exit, including no git at all. */
|
|
13645
|
-
function git(root, args) {
|
|
13646
|
-
const r = spawnSync("git", [...args], {
|
|
13647
|
-
cwd: root,
|
|
13648
|
-
encoding: "utf8",
|
|
13649
|
-
maxBuffer: MAX_BUFFER
|
|
13650
|
-
});
|
|
13651
|
-
return r.status === 0 ? r.stdout : null;
|
|
13652
|
-
}
|
|
13653
|
-
function historicLedger(root) {
|
|
13654
|
-
const inside = git(root, ["rev-parse", "--is-inside-work-tree"]);
|
|
13655
|
-
if (inside === null || inside.trim() !== "true") return {
|
|
13656
|
-
repository: false,
|
|
13657
|
-
shallow: false,
|
|
13658
|
-
entries: null,
|
|
13659
|
-
unreadable: null
|
|
13660
|
-
};
|
|
13661
|
-
const shallow = (git(root, ["rev-parse", "--is-shallow-repository"]) ?? "").trim() === "true";
|
|
13662
|
-
const born = git(root, [
|
|
13663
|
-
"rev-parse",
|
|
13664
|
-
"--verify",
|
|
13665
|
-
"--quiet",
|
|
13666
|
-
"HEAD"
|
|
13667
|
-
]) !== null;
|
|
13668
|
-
const entries = shallow ? null : born ? historicEntries(root) : [];
|
|
13669
|
-
return {
|
|
13670
|
-
repository: true,
|
|
13671
|
-
shallow,
|
|
13672
|
-
entries,
|
|
13673
|
-
unreadable: entries !== null ? null : shallow ? "shallow" : "unreadable"
|
|
13674
|
-
};
|
|
13675
|
-
}
|
|
13676
|
-
/**
|
|
13677
|
-
* Every id history has ever recorded, each with the text it carried the FIRST
|
|
13678
|
-
* time it was written. A version that parses contributes each entry's digest,
|
|
13679
|
-
* so an entry EDITED in place is caught, not only one deleted; a version that
|
|
13680
|
-
* no longer parses still contributes its ids, read permissively — the point
|
|
13681
|
-
* there is that an id once written never disappears.
|
|
13682
|
-
*
|
|
13683
|
-
* FIRST, not every: keying this by `id\tdigest` kept one baseline entry per
|
|
13684
|
-
* version an id ever had, so a tamper that was COMMITTED and then UNDONE left
|
|
13685
|
-
* two digests for one id, the restored entry matched only one of them, and
|
|
13686
|
-
* `ksor-ledger-amended` fired for good. The record became permanently
|
|
13687
|
-
* unbuildable — by a tamper that had already been put right — and the only
|
|
13688
|
-
* escape was rewriting git history, which is not a remedy a refusal may
|
|
13689
|
-
* demand (found in review, 2026-08-25).
|
|
13690
|
-
*
|
|
13691
|
-
* Taking the OLDEST is what makes the guarantee both enforceable and
|
|
13692
|
-
* escapable. It still refuses a committed tamper (the baseline is what the
|
|
13693
|
-
* entry said when it was written, so committing the edit does not launder
|
|
13694
|
-
* it), and the remedy it names — put the entry back — now actually clears
|
|
13695
|
-
* it. Taking the NEWEST would have done the opposite on both counts.
|
|
13696
|
-
*/
|
|
13697
|
-
function historicEntries(root) {
|
|
13698
|
-
const atRoot = `${(git(root, ["rev-parse", "--show-prefix"]) ?? "").trim()}${LEDGER}`;
|
|
13699
|
-
const commits = git(root, [
|
|
13700
|
-
"log",
|
|
13701
|
-
"--full-history",
|
|
13702
|
-
"--topo-order",
|
|
13703
|
-
"--format=%H",
|
|
13704
|
-
"--",
|
|
13705
|
-
LEDGER
|
|
13706
|
-
]);
|
|
13707
|
-
if (commits === null) return null;
|
|
13708
|
-
const seen = /* @__PURE__ */ new Map();
|
|
13709
|
-
for (const sha of commits.split("\n").filter((s) => s !== "")) {
|
|
13710
|
-
const text = git(root, ["show", `${sha}:${atRoot}`]);
|
|
13711
|
-
if (text === null) {
|
|
13712
|
-
const listed = git(root, [
|
|
13713
|
-
"ls-tree",
|
|
13714
|
-
"--full-tree",
|
|
13715
|
-
"--name-only",
|
|
13716
|
-
sha,
|
|
13717
|
-
"--",
|
|
13718
|
-
atRoot
|
|
13719
|
-
]);
|
|
13720
|
-
if (listed !== null && listed.trim() === "") continue;
|
|
13721
|
-
return null;
|
|
13722
|
-
}
|
|
13723
|
-
const where = sha.slice(0, 7);
|
|
13724
|
-
const parsed = parseLedger(text, LEDGER);
|
|
13725
|
-
if (parsed.ok) {
|
|
13726
|
-
for (const entry of parsed.ledger.entries) seen.set(entry.id, {
|
|
13727
|
-
id: entry.id,
|
|
13728
|
-
digest: entryDigest(entry),
|
|
13729
|
-
entry,
|
|
13730
|
-
where
|
|
13731
|
-
});
|
|
13732
|
-
continue;
|
|
13733
|
-
}
|
|
13734
|
-
for (const m of text.matchAll(/^\s*(?:-\s+)?id:\s*["']?([^\s"']+)/gm)) {
|
|
13735
|
-
const id = m[1] ?? "";
|
|
13736
|
-
if (!seen.has(id)) seen.set(id, {
|
|
13737
|
-
id,
|
|
13738
|
-
digest: null,
|
|
13739
|
-
where
|
|
13740
|
-
});
|
|
13741
|
-
}
|
|
13742
|
-
}
|
|
13743
|
-
return [...seen.values()].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
13744
|
-
}
|
|
13745
13751
|
/** The inputs a projection reads; nothing else moves `source_commit` (the lock itself included). */
|
|
13746
13752
|
const INPUTS = [
|
|
13747
13753
|
"knowledge",
|
|
@@ -12672,6 +12672,139 @@ function nearMissOf(baseName) {
|
|
|
12672
12672
|
return null;
|
|
12673
12673
|
}
|
|
12674
12674
|
/**
|
|
12675
|
+
* The takedown ledger's OTHER baseline: every entry any COMMITTED version of
|
|
12676
|
+
* `.ksor/takedowns.yaml` has ever carried.
|
|
12677
|
+
*
|
|
12678
|
+
* The committed lock is a baseline too, and a good one — it holds each entry's
|
|
12679
|
+
* digest, so an entry retargeted in place is caught. What it cannot do is prove
|
|
12680
|
+
* that an entry was never deleted, because the lock travels in the SAME change
|
|
12681
|
+
* as the ledger: delete the row, recompute `ledger_sha256`, empty
|
|
12682
|
+
* `ledger_entries`, and the two agree with each other about a denial that is
|
|
12683
|
+
* gone. Only history remembers.
|
|
12684
|
+
*
|
|
12685
|
+
* The baseline may be INCOMPLETE only if it SAYS so: every version history
|
|
12686
|
+
* holds is read, or `entries` comes back null and the caller reports that it
|
|
12687
|
+
* could not verify. A version silently skipped would contribute neither
|
|
12688
|
+
* digests nor ids while the answer still read "verified"
|
|
12689
|
+
* (`git-ledger.integration.test.ts`).
|
|
12690
|
+
*
|
|
12691
|
+
* This lives in the record module because THREE surfaces need the same answer —
|
|
12692
|
+
* `ksor build`, the emitted checker, and the site's stage (decision 19: a
|
|
12693
|
+
* surface that refuses must refuse on both surfaces). Plain `git log` / `git
|
|
12694
|
+
* show`, so nothing here needs installing.
|
|
12695
|
+
*/
|
|
12696
|
+
const LEDGER = ".ksor/takedowns.yaml";
|
|
12697
|
+
/**
|
|
12698
|
+
* `spawnSync` defaults to a 1 MB stdout buffer, and past it the child is KILLED
|
|
12699
|
+
* — `status` comes back null, so the query reads as a failure. A ledger with a
|
|
12700
|
+
* few thousand entries, or one entry carrying a long reason, clears 1 MB
|
|
12701
|
+
* easily, and the version was then dropped from the baseline while the caller
|
|
12702
|
+
* was still told history had been verified. The ceiling stays finite on
|
|
12703
|
+
* purpose: past it this returns null, which is a state the caller SAYS.
|
|
12704
|
+
*/
|
|
12705
|
+
const MAX_BUFFER$1 = 67108864;
|
|
12706
|
+
/** One git query, read-only. Null on any non-zero exit, including no git at all. */
|
|
12707
|
+
function git(root, args) {
|
|
12708
|
+
const r = spawnSync("git", [...args], {
|
|
12709
|
+
cwd: root,
|
|
12710
|
+
encoding: "utf8",
|
|
12711
|
+
maxBuffer: MAX_BUFFER$1
|
|
12712
|
+
});
|
|
12713
|
+
return r.status === 0 ? r.stdout : null;
|
|
12714
|
+
}
|
|
12715
|
+
function historicLedger(root) {
|
|
12716
|
+
const inside = git(root, ["rev-parse", "--is-inside-work-tree"]);
|
|
12717
|
+
if (inside === null || inside.trim() !== "true") return {
|
|
12718
|
+
repository: false,
|
|
12719
|
+
shallow: false,
|
|
12720
|
+
entries: null,
|
|
12721
|
+
unreadable: null
|
|
12722
|
+
};
|
|
12723
|
+
const shallow = (git(root, ["rev-parse", "--is-shallow-repository"]) ?? "").trim() === "true";
|
|
12724
|
+
const born = git(root, [
|
|
12725
|
+
"rev-parse",
|
|
12726
|
+
"--verify",
|
|
12727
|
+
"--quiet",
|
|
12728
|
+
"HEAD"
|
|
12729
|
+
]) !== null;
|
|
12730
|
+
const entries = shallow ? null : born ? historicEntries(root) : [];
|
|
12731
|
+
return {
|
|
12732
|
+
repository: true,
|
|
12733
|
+
shallow,
|
|
12734
|
+
entries,
|
|
12735
|
+
unreadable: entries !== null ? null : shallow ? "shallow" : "unreadable"
|
|
12736
|
+
};
|
|
12737
|
+
}
|
|
12738
|
+
/**
|
|
12739
|
+
* Every id history has ever recorded, each with the text it carried the FIRST
|
|
12740
|
+
* time it was written. A version that parses contributes each entry's digest,
|
|
12741
|
+
* so an entry EDITED in place is caught, not only one deleted; a version that
|
|
12742
|
+
* no longer parses still contributes its ids, read permissively — the point
|
|
12743
|
+
* there is that an id once written never disappears.
|
|
12744
|
+
*
|
|
12745
|
+
* FIRST, not every: keying this by `id\tdigest` kept one baseline entry per
|
|
12746
|
+
* version an id ever had, so a tamper that was COMMITTED and then UNDONE left
|
|
12747
|
+
* two digests for one id, the restored entry matched only one of them, and
|
|
12748
|
+
* `ksor-ledger-amended` fired for good. The record became permanently
|
|
12749
|
+
* unbuildable — by a tamper that had already been put right — and the only
|
|
12750
|
+
* escape was rewriting git history, which is not a remedy a refusal may
|
|
12751
|
+
* demand (found in review, 2026-08-25).
|
|
12752
|
+
*
|
|
12753
|
+
* Taking the OLDEST is what makes the guarantee both enforceable and
|
|
12754
|
+
* escapable. It still refuses a committed tamper (the baseline is what the
|
|
12755
|
+
* entry said when it was written, so committing the edit does not launder
|
|
12756
|
+
* it), and the remedy it names — put the entry back — now actually clears
|
|
12757
|
+
* it. Taking the NEWEST would have done the opposite on both counts.
|
|
12758
|
+
*/
|
|
12759
|
+
function historicEntries(root) {
|
|
12760
|
+
const atRoot = `${(git(root, ["rev-parse", "--show-prefix"]) ?? "").trim()}${LEDGER}`;
|
|
12761
|
+
const commits = git(root, [
|
|
12762
|
+
"log",
|
|
12763
|
+
"--full-history",
|
|
12764
|
+
"--topo-order",
|
|
12765
|
+
"--format=%H",
|
|
12766
|
+
"--",
|
|
12767
|
+
LEDGER
|
|
12768
|
+
]);
|
|
12769
|
+
if (commits === null) return null;
|
|
12770
|
+
const seen = /* @__PURE__ */ new Map();
|
|
12771
|
+
for (const sha of commits.split("\n").filter((s) => s !== "")) {
|
|
12772
|
+
const text = git(root, ["show", `${sha}:${atRoot}`]);
|
|
12773
|
+
if (text === null) {
|
|
12774
|
+
const listed = git(root, [
|
|
12775
|
+
"ls-tree",
|
|
12776
|
+
"--full-tree",
|
|
12777
|
+
"--name-only",
|
|
12778
|
+
sha,
|
|
12779
|
+
"--",
|
|
12780
|
+
atRoot
|
|
12781
|
+
]);
|
|
12782
|
+
if (listed !== null && listed.trim() === "") continue;
|
|
12783
|
+
return null;
|
|
12784
|
+
}
|
|
12785
|
+
const where = sha.slice(0, 7);
|
|
12786
|
+
const parsed = parseLedger(text, LEDGER);
|
|
12787
|
+
if (parsed.ok) {
|
|
12788
|
+
for (const entry of parsed.ledger.entries) seen.set(entry.id, {
|
|
12789
|
+
id: entry.id,
|
|
12790
|
+
digest: entryDigest(entry),
|
|
12791
|
+
entry,
|
|
12792
|
+
where
|
|
12793
|
+
});
|
|
12794
|
+
continue;
|
|
12795
|
+
}
|
|
12796
|
+
for (const m of text.matchAll(/^\s*(?:-\s+)?id:\s*["']?([^\s"']+)/gm)) {
|
|
12797
|
+
const id = m[1] ?? "";
|
|
12798
|
+
if (!seen.has(id)) seen.set(id, {
|
|
12799
|
+
id,
|
|
12800
|
+
digest: null,
|
|
12801
|
+
where
|
|
12802
|
+
});
|
|
12803
|
+
}
|
|
12804
|
+
}
|
|
12805
|
+
return [...seen.values()].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
12806
|
+
}
|
|
12807
|
+
/**
|
|
12675
12808
|
* The widening rule: a link, a `ksor.superseded_by` pointer or a companion
|
|
12676
12809
|
* body may reach a target whose audience contains `public` or contains every
|
|
12677
12810
|
* identifier in the source's — then every reader of the source can read the
|
|
@@ -13537,6 +13670,11 @@ function checkAgainstPolicy(concept, policy, refusals) {
|
|
|
13537
13670
|
}
|
|
13538
13671
|
const hex64 = string().regex(/^[0-9a-f]{64}$/, "a sha256 hex digest");
|
|
13539
13672
|
const viewerList = array(string().min(1));
|
|
13673
|
+
const bundleEntry = object({
|
|
13674
|
+
viewer: string().min(1),
|
|
13675
|
+
sha256: hex64,
|
|
13676
|
+
files: number().int().nonnegative()
|
|
13677
|
+
}).strict();
|
|
13540
13678
|
const lockSchema = object({
|
|
13541
13679
|
format: literal(1),
|
|
13542
13680
|
build_id: string().regex(/^sha256:[0-9a-f]{64}$/),
|
|
@@ -13584,7 +13722,8 @@ const lockSchema = object({
|
|
|
13584
13722
|
indexes: array(object({
|
|
13585
13723
|
path: string().min(1),
|
|
13586
13724
|
sha256: hex64
|
|
13587
|
-
}).strict())
|
|
13725
|
+
}).strict()),
|
|
13726
|
+
bundles: array(bundleEntry)
|
|
13588
13727
|
}).strict();
|
|
13589
13728
|
function parseLock(text) {
|
|
13590
13729
|
let value;
|
|
@@ -13609,139 +13748,6 @@ function parseLock(text) {
|
|
|
13609
13748
|
lock: parsed.data
|
|
13610
13749
|
};
|
|
13611
13750
|
}
|
|
13612
|
-
/**
|
|
13613
|
-
* The takedown ledger's OTHER baseline: every entry any COMMITTED version of
|
|
13614
|
-
* `.ksor/takedowns.yaml` has ever carried.
|
|
13615
|
-
*
|
|
13616
|
-
* The committed lock is a baseline too, and a good one — it holds each entry's
|
|
13617
|
-
* digest, so an entry retargeted in place is caught. What it cannot do is prove
|
|
13618
|
-
* that an entry was never deleted, because the lock travels in the SAME change
|
|
13619
|
-
* as the ledger: delete the row, recompute `ledger_sha256`, empty
|
|
13620
|
-
* `ledger_entries`, and the two agree with each other about a denial that is
|
|
13621
|
-
* gone. Only history remembers.
|
|
13622
|
-
*
|
|
13623
|
-
* The baseline may be INCOMPLETE only if it SAYS so: every version history
|
|
13624
|
-
* holds is read, or `entries` comes back null and the caller reports that it
|
|
13625
|
-
* could not verify. A version silently skipped would contribute neither
|
|
13626
|
-
* digests nor ids while the answer still read "verified"
|
|
13627
|
-
* (`git-ledger.integration.test.ts`).
|
|
13628
|
-
*
|
|
13629
|
-
* This lives in the record module because THREE surfaces need the same answer —
|
|
13630
|
-
* `ksor build`, the emitted checker, and the site's stage (decision 19: a
|
|
13631
|
-
* surface that refuses must refuse on both surfaces). Plain `git log` / `git
|
|
13632
|
-
* show`, so nothing here needs installing.
|
|
13633
|
-
*/
|
|
13634
|
-
const LEDGER = ".ksor/takedowns.yaml";
|
|
13635
|
-
/**
|
|
13636
|
-
* `spawnSync` defaults to a 1 MB stdout buffer, and past it the child is KILLED
|
|
13637
|
-
* — `status` comes back null, so the query reads as a failure. A ledger with a
|
|
13638
|
-
* few thousand entries, or one entry carrying a long reason, clears 1 MB
|
|
13639
|
-
* easily, and the version was then dropped from the baseline while the caller
|
|
13640
|
-
* was still told history had been verified. The ceiling stays finite on
|
|
13641
|
-
* purpose: past it this returns null, which is a state the caller SAYS.
|
|
13642
|
-
*/
|
|
13643
|
-
const MAX_BUFFER = 67108864;
|
|
13644
|
-
/** One git query, read-only. Null on any non-zero exit, including no git at all. */
|
|
13645
|
-
function git(root, args) {
|
|
13646
|
-
const r = spawnSync("git", [...args], {
|
|
13647
|
-
cwd: root,
|
|
13648
|
-
encoding: "utf8",
|
|
13649
|
-
maxBuffer: MAX_BUFFER
|
|
13650
|
-
});
|
|
13651
|
-
return r.status === 0 ? r.stdout : null;
|
|
13652
|
-
}
|
|
13653
|
-
function historicLedger(root) {
|
|
13654
|
-
const inside = git(root, ["rev-parse", "--is-inside-work-tree"]);
|
|
13655
|
-
if (inside === null || inside.trim() !== "true") return {
|
|
13656
|
-
repository: false,
|
|
13657
|
-
shallow: false,
|
|
13658
|
-
entries: null,
|
|
13659
|
-
unreadable: null
|
|
13660
|
-
};
|
|
13661
|
-
const shallow = (git(root, ["rev-parse", "--is-shallow-repository"]) ?? "").trim() === "true";
|
|
13662
|
-
const born = git(root, [
|
|
13663
|
-
"rev-parse",
|
|
13664
|
-
"--verify",
|
|
13665
|
-
"--quiet",
|
|
13666
|
-
"HEAD"
|
|
13667
|
-
]) !== null;
|
|
13668
|
-
const entries = shallow ? null : born ? historicEntries(root) : [];
|
|
13669
|
-
return {
|
|
13670
|
-
repository: true,
|
|
13671
|
-
shallow,
|
|
13672
|
-
entries,
|
|
13673
|
-
unreadable: entries !== null ? null : shallow ? "shallow" : "unreadable"
|
|
13674
|
-
};
|
|
13675
|
-
}
|
|
13676
|
-
/**
|
|
13677
|
-
* Every id history has ever recorded, each with the text it carried the FIRST
|
|
13678
|
-
* time it was written. A version that parses contributes each entry's digest,
|
|
13679
|
-
* so an entry EDITED in place is caught, not only one deleted; a version that
|
|
13680
|
-
* no longer parses still contributes its ids, read permissively — the point
|
|
13681
|
-
* there is that an id once written never disappears.
|
|
13682
|
-
*
|
|
13683
|
-
* FIRST, not every: keying this by `id\tdigest` kept one baseline entry per
|
|
13684
|
-
* version an id ever had, so a tamper that was COMMITTED and then UNDONE left
|
|
13685
|
-
* two digests for one id, the restored entry matched only one of them, and
|
|
13686
|
-
* `ksor-ledger-amended` fired for good. The record became permanently
|
|
13687
|
-
* unbuildable — by a tamper that had already been put right — and the only
|
|
13688
|
-
* escape was rewriting git history, which is not a remedy a refusal may
|
|
13689
|
-
* demand (found in review, 2026-08-25).
|
|
13690
|
-
*
|
|
13691
|
-
* Taking the OLDEST is what makes the guarantee both enforceable and
|
|
13692
|
-
* escapable. It still refuses a committed tamper (the baseline is what the
|
|
13693
|
-
* entry said when it was written, so committing the edit does not launder
|
|
13694
|
-
* it), and the remedy it names — put the entry back — now actually clears
|
|
13695
|
-
* it. Taking the NEWEST would have done the opposite on both counts.
|
|
13696
|
-
*/
|
|
13697
|
-
function historicEntries(root) {
|
|
13698
|
-
const atRoot = `${(git(root, ["rev-parse", "--show-prefix"]) ?? "").trim()}${LEDGER}`;
|
|
13699
|
-
const commits = git(root, [
|
|
13700
|
-
"log",
|
|
13701
|
-
"--full-history",
|
|
13702
|
-
"--topo-order",
|
|
13703
|
-
"--format=%H",
|
|
13704
|
-
"--",
|
|
13705
|
-
LEDGER
|
|
13706
|
-
]);
|
|
13707
|
-
if (commits === null) return null;
|
|
13708
|
-
const seen = /* @__PURE__ */ new Map();
|
|
13709
|
-
for (const sha of commits.split("\n").filter((s) => s !== "")) {
|
|
13710
|
-
const text = git(root, ["show", `${sha}:${atRoot}`]);
|
|
13711
|
-
if (text === null) {
|
|
13712
|
-
const listed = git(root, [
|
|
13713
|
-
"ls-tree",
|
|
13714
|
-
"--full-tree",
|
|
13715
|
-
"--name-only",
|
|
13716
|
-
sha,
|
|
13717
|
-
"--",
|
|
13718
|
-
atRoot
|
|
13719
|
-
]);
|
|
13720
|
-
if (listed !== null && listed.trim() === "") continue;
|
|
13721
|
-
return null;
|
|
13722
|
-
}
|
|
13723
|
-
const where = sha.slice(0, 7);
|
|
13724
|
-
const parsed = parseLedger(text, LEDGER);
|
|
13725
|
-
if (parsed.ok) {
|
|
13726
|
-
for (const entry of parsed.ledger.entries) seen.set(entry.id, {
|
|
13727
|
-
id: entry.id,
|
|
13728
|
-
digest: entryDigest(entry),
|
|
13729
|
-
entry,
|
|
13730
|
-
where
|
|
13731
|
-
});
|
|
13732
|
-
continue;
|
|
13733
|
-
}
|
|
13734
|
-
for (const m of text.matchAll(/^\s*(?:-\s+)?id:\s*["']?([^\s"']+)/gm)) {
|
|
13735
|
-
const id = m[1] ?? "";
|
|
13736
|
-
if (!seen.has(id)) seen.set(id, {
|
|
13737
|
-
id,
|
|
13738
|
-
digest: null,
|
|
13739
|
-
where
|
|
13740
|
-
});
|
|
13741
|
-
}
|
|
13742
|
-
}
|
|
13743
|
-
return [...seen.values()].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
13744
|
-
}
|
|
13745
13751
|
/** The inputs a projection reads; nothing else moves `source_commit` (the lock itself included). */
|
|
13746
13752
|
const INPUTS = [
|
|
13747
13753
|
"knowledge",
|
|
@@ -497,9 +497,14 @@ runs over every entry in the ledger at `pnpm check`, `ksor build` and ingest, so
|
|
|
497
497
|
a line appended by hand in a pull request is refused exactly as the verb would
|
|
498
498
|
refuse it. The read-only modes
|
|
499
499
|
(`--list`, `--ledger`) need no actor — nobody is performing an act by looking.
|
|
500
|
-
They do not need a database either:
|
|
501
|
-
|
|
502
|
-
|
|
500
|
+
They do not need a database either: `--ledger` always reads the committed
|
|
501
|
+
`.ksor/takedowns.yaml`, which is the record of every act, and `--list` reads
|
|
502
|
+
the door's denylist rows when `KSOR_DB_URL` is set and otherwise the ledger's
|
|
503
|
+
denials, each labelled `not applied (no database)` because no row exists for
|
|
504
|
+
a door to refuse on. A denial itself, on this record with `KSOR_DB_URL` unset,
|
|
505
|
+
is refused by name (`ksor-takedown-dsn-missing`) unless `--file-only` records
|
|
506
|
+
the entry alone — `--apply` writes its row later, where the database is
|
|
507
|
+
reachable.
|
|
503
508
|
|
|
504
509
|
**The MCP door stops serving it immediately. The SITE stops at its next
|
|
505
510
|
build** — the site reads the committed ledger (`.ksor/takedowns.yaml`), not
|
|
@@ -516,7 +521,11 @@ ROOT, never `system/site/`), and every other host just serves the folder.
|
|
|
516
521
|
record checked, `build.lock.json` written (commit it: it is the provenance
|
|
517
522
|
every machine artefact stamps) — and a refusal stops the build before a byte
|
|
518
523
|
is written. `ksor build --strict` additionally refuses an uncommitted input
|
|
519
|
-
(`ksor-build-dirty`), which is the posture for a release.
|
|
524
|
+
(`ksor-build-dirty`), which is the posture for a release. `ksor build
|
|
525
|
+
--bundles` also writes one OKF bundle per viewer under
|
|
526
|
+
`.ksor/out/bundles/<viewer>/` (gitignored) — only what that viewer's machine
|
|
527
|
+
surfaces publish, readable by any OKF consumer with no ksor in the loop — for
|
|
528
|
+
handing the record to another system.
|
|
520
529
|
`KSOR_BASE_PATH=/repo pnpm build` targets sub-path hosting. With audiences
|
|
521
530
|
registered in `.ksor/governance.yaml`, plain `pnpm build` is the `public`
|
|
522
531
|
viewer; `KSOR_AUDIENCE=public,<audience> pnpm build` — a comma list, always
|
|
@@ -607,7 +616,14 @@ CI — and a first deploy without it serves an empty record. Full walkthrough:
|
|
|
607
616
|
audiences registered in `.ksor/governance.yaml`; never omitted, never
|
|
608
617
|
inferred). A `stable` document carries `generated: { by, at }` and
|
|
609
618
|
`ksor.approval: { by, at }` by an actor the policy authorises, with
|
|
610
|
-
`generated.at` no later than the approval
|
|
619
|
+
`generated.at` no later than the approval — and `generated.at` dates the
|
|
620
|
+
TEXT: edit a stable document's body and `ksor build` and `ksor ingest`
|
|
621
|
+
compare it against every committed version that was stable, and refuse
|
|
622
|
+
`ksor-generated-stale` until you move the stamp PAST the edit and re-approve
|
|
623
|
+
— leaving the stamp alone and backdating it are refused alike, because
|
|
624
|
+
neither advances it (a frontmatter-only edit, such as adding a `verified`
|
|
625
|
+
entry, is not a body change; `pnpm check` reads no document history, so it passes an
|
|
626
|
+
edit the build will refuse); a `deprecated` one carries
|
|
611
627
|
`ksor.deprecated: { by, at }` and usually `ksor.superseded_by: <id>`
|
|
612
628
|
(a stable document every reader of this one may read). Optional: `order`
|
|
613
629
|
(reading position), `sources` (`{ id, resource, title }`, cited in the body
|
|
@@ -259,7 +259,9 @@ Never copy a floor from another corpus.
|
|
|
259
259
|
It also prints its own caveat, and it is worth reading: the probes it writes are
|
|
260
260
|
derived from your passages, so they share vocabulary a real question will not.
|
|
261
261
|
The floor it reports is an upper bound on separation until you check it against
|
|
262
|
-
questions
|
|
262
|
+
questions a person wrote: in-corpus ones with `--queries-file`, and — because
|
|
263
|
+
the built-in out-of-corpus probes are all far-domain — questions just outside
|
|
264
|
+
your scope with `--ooc-file`.
|
|
263
265
|
|
|
264
266
|
A rerun on an unchanged record costs nothing: no new generation, no embedding,
|
|
265
267
|
no rows. Edit a document and the next run picks up exactly that change.
|
|
@@ -724,6 +726,7 @@ map rather than a substitute.
|
|
|
724
726
|
| the home page and `/llms.txt` are empty | every document is still a draft — correct, not broken | approve one and rebuild |
|
|
725
727
|
| `ksor-record-empty` | every document was deleted — a record is never empty, so nothing was written | add one document of your own (or restore one from git) before deleting the last starter |
|
|
726
728
|
| `ksor-approver-unauthorised` | a document is approved by an actor `.ksor/governance.yaml` no longer names — usually `human:you` after the interview | re-attribute the approval to your handle, or restore the actor to `approval_authorities` |
|
|
729
|
+
| `ksor-generated-stale` at `pnpm build` or `pnpm refresh` | a `stable` document's body changed since a commit where it was stable, and `generated.at` was not advanced past that commit's (left alone, or moved backward) — the stamp dates the text, and `pnpm check` reads no document history, so it passed | set `generated.at` to an instant after the edit and re-approve (`ksor.approval.at` may not precede it) |
|
|
727
730
|
| a new document never appears on the built site | drafts reach no built surface at all | publish it — `status: stable` plus both governance keys |
|
|
728
731
|
| an expired document still shows on the site but not through the door | the static build evaluated `stale_after` at build time | rebuild and redeploy; schedule a rebuild if you use it |
|
|
729
732
|
| Vercel: `no services are declared` | Root Directory was auto-filled with `system/site` | set it to `./` |
|
|
@@ -66,6 +66,9 @@ KSOR_AUTH=disabled-local
|
|
|
66
66
|
# NEXT_PUBLIC_KSOR_SSO_URL=https://your-sso.example.com
|
|
67
67
|
# NEXT_PUBLIC_KSOR_OAUTH_CLIENT_ID=
|
|
68
68
|
# NEXT_PUBLIC_KSOR_OAUTH_REDIRECT_URI=https://your-site.example.com/auth/callback
|
|
69
|
+
#
|
|
70
|
+
# Next.js telemetry is already off: the site's build and dev scripts run next
|
|
71
|
+
# through system/site/next-no-telemetry.mjs, which sets NEXT_TELEMETRY_DISABLED=1.
|
|
69
72
|
|
|
70
73
|
# ── Production knobs ────────────────────────────────────────────────────────
|
|
71
74
|
# Unset is fine for a local run; each one matters once this serves for real.
|