@panaversity/ksor 0.0.40 → 0.0.41
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 +869 -0
- package/README.md +11 -7
- package/dist/checker/check-main.mjs +14049 -0
- package/dist/cli.mjs +11528 -5213
- package/dist/gateway-api-CF4ED9_g-BQusM_dK.mjs +10895 -0
- package/dist/gateway.d.mts +52 -13
- package/dist/gateway.mjs +2 -2
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{src-pl4aOpVs.mjs → src-dqpI-p1a.mjs} +1 -0
- package/docs/authorization.md +8 -6
- package/docs/deploying.md +36 -25
- package/docs/index.md +26 -13
- package/docs/ingesting.md +70 -22
- package/docs/tool-surface.md +69 -16
- package/package.json +4 -3
- package/schema/migrations/2.4-2.5__okf-profile.sql +114 -0
- package/schema/schema.sql +77 -14
- package/templates/scaffold/.agents/skills/add-sources/SKILL.md +63 -18
- package/templates/scaffold/.agents/skills/format-checker/SKILL.md +42 -33
- package/templates/scaffold/.agents/skills/format-checker/check.mjs +13827 -1314
- package/templates/scaffold/.agents/skills/intake-interview/SKILL.md +65 -27
- package/templates/scaffold/.agents/skills/make-slides/SKILL.md +7 -5
- package/templates/scaffold/.agents/skills/make-summary/SKILL.md +13 -6
- package/templates/scaffold/.claude/skills/add-sources/SKILL.md +63 -18
- package/templates/scaffold/.claude/skills/format-checker/SKILL.md +42 -33
- package/templates/scaffold/.claude/skills/format-checker/check.mjs +13827 -1314
- package/templates/scaffold/.claude/skills/intake-interview/SKILL.md +65 -27
- package/templates/scaffold/.claude/skills/make-slides/SKILL.md +7 -5
- package/templates/scaffold/.claude/skills/make-summary/SKILL.md +13 -6
- package/templates/scaffold/.github/workflows/validate.yml +9 -1
- package/templates/scaffold/.ksor/governance.yaml +17 -0
- package/templates/scaffold/AGENTS.md +234 -113
- package/templates/scaffold/Dockerfile +5 -1
- package/templates/scaffold/README.md +160 -42
- package/templates/scaffold/env.example +37 -6
- package/templates/scaffold/gitignore +13 -8
- package/templates/scaffold/instance.md +21 -17
- package/templates/scaffold/knowledge/governance-ladder.md +6 -2
- package/templates/scaffold/knowledge/index.md +9 -0
- package/templates/scaffold/knowledge/surfaces/for-agents.md +7 -6
- package/templates/scaffold/knowledge/surfaces/for-people.md +7 -6
- package/templates/scaffold/knowledge/surfaces/index.md +4 -20
- package/templates/scaffold/knowledge/surfaces/overview.md +25 -0
- package/templates/scaffold/knowledge/what-is-a-ksor.md +6 -5
- package/templates/scaffold/knowledge/what-is-a-ksor.summary.md +4 -0
- package/templates/scaffold/package.json +3 -4
- package/templates/scaffold/pnpm-lock.yaml +3 -0
- package/templates/scaffold/system/gateways/content.ts +13 -0
- package/templates/scaffold/system/site/app/(home)/page.tsx +2 -2
- package/templates/scaffold/system/site/app/.well-known/mcp/server.json/route.ts +10 -0
- package/templates/scaffold/system/site/app/docs/[[...slug]]/page.tsx +126 -91
- package/templates/scaffold/system/site/app/global.css +13 -5
- package/templates/scaffold/system/site/app/layout.tsx +8 -3
- package/templates/scaffold/system/site/app/llms-full.txt/route.ts +13 -7
- package/templates/scaffold/system/site/app/llms.txt/route.ts +12 -7
- package/templates/scaffold/system/site/app/md/[[...slug]]/route.ts +26 -25
- package/templates/scaffold/system/site/components/footer-mark.tsx +3 -2
- package/templates/scaffold/system/site/components/governance.tsx +205 -87
- package/templates/scaffold/system/site/components/record-index.tsx +5 -5
- package/templates/scaffold/system/site/components/record-stack.tsx +10 -9
- package/templates/scaffold/system/site/components/sidebar-status.tsx +19 -18
- package/templates/scaffold/system/site/lib/attachment-rule.ts +6 -1
- package/templates/scaffold/system/site/lib/attachments.ts +0 -28
- package/templates/scaffold/system/site/lib/audience-rule.ts +15 -21
- package/templates/scaffold/system/site/lib/audience.ts +42 -146
- package/templates/scaffold/system/site/lib/embed-rule.ts +9 -0
- package/templates/scaffold/system/site/lib/governance.ts +339 -225
- package/templates/scaffold/system/site/lib/index-routes.ts +125 -0
- package/templates/scaffold/system/site/lib/lifecycle-rule.ts +52 -0
- package/templates/scaffold/system/site/lib/lock.ts +282 -0
- package/templates/scaffold/system/site/lib/order-rule.ts +37 -0
- package/templates/scaffold/system/site/lib/record-href.ts +68 -0
- package/templates/scaffold/system/site/lib/record-link.tsx +26 -0
- package/templates/scaffold/system/site/lib/rules-version.ts +11 -0
- package/templates/scaffold/system/site/lib/shared.ts +67 -104
- package/templates/scaffold/system/site/lib/sim-rule.ts +49 -0
- package/templates/scaffold/system/site/lib/source.ts +256 -186
- package/templates/scaffold/system/site/lib/stage-knowledge.ts +566 -492
- package/templates/scaffold/system/site/lib/stage-manifest.ts +128 -0
- package/templates/scaffold/system/site/package.json +1 -0
- package/templates/scaffold/system/site/record/actor.ts +23 -0
- package/templates/scaffold/system/site/record/check.ts +571 -0
- package/templates/scaffold/system/site/record/citations.ts +312 -0
- package/templates/scaffold/system/site/record/frontmatter.ts +134 -0
- package/templates/scaffold/system/site/record/git-ledger.ts +171 -0
- package/templates/scaffold/system/site/record/hygiene.ts +320 -0
- package/templates/scaffold/system/site/record/index-file.ts +150 -0
- package/templates/scaffold/system/site/record/index.ts +103 -0
- package/templates/scaffold/system/site/record/instance.ts +257 -0
- package/templates/scaffold/system/site/record/instant.ts +43 -0
- package/templates/scaffold/system/site/record/ledger.ts +694 -0
- package/templates/scaffold/system/site/record/load.ts +129 -0
- package/templates/scaffold/system/site/record/lock.ts +306 -0
- package/templates/scaffold/system/site/record/near-miss.ts +37 -0
- package/templates/scaffold/system/site/record/policy.ts +414 -0
- package/templates/scaffold/system/site/record/profile.ts +535 -0
- package/templates/scaffold/system/site/record/refusal.ts +106 -0
- package/templates/scaffold/system/site/record/yaml-file.ts +103 -0
- package/templates/scaffold/system/site/source.config.ts +77 -22
- package/dist/gateway-api-CmIthmJS-IUA9qS-T.mjs +0 -3225
- package/templates/scaffold/system/site/lib/denial-rule.ts +0 -220
- package/templates/scaffold/system/site/lib/page-order.ts +0 -93
|
@@ -16,29 +16,23 @@
|
|
|
16
16
|
* this file is the shared implementation of the TypeScript half.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
/**
|
|
20
|
+
* The overlap rule (record spec §2.4): a concept holds a LIST of audience
|
|
21
|
+
* identifiers, a viewer holds a list that always includes `public`, and the
|
|
22
|
+
* concept is visible when the two overlap. Rank moves to the viewer,
|
|
23
|
+
* membership stays on the document. Omission is a refusal upstream, never a
|
|
24
|
+
* default here — an empty list on either side is visible to nobody.
|
|
25
|
+
*/
|
|
26
|
+
export function overlaps(viewer: readonly string[], audience: readonly string[]): boolean {
|
|
27
|
+
return audience.some((a) => viewer.includes(a));
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
+
* The widening rule: a link, a `ksor.superseded_by` pointer or a companion
|
|
32
|
+
* body may reach a target whose audience contains `public` or contains every
|
|
33
|
+
* identifier in the source's — then every reader of the source can read the
|
|
34
|
+
* target. `[internal]` → `[public]` passes; `[public]` → `[internal]` refuses.
|
|
31
35
|
*/
|
|
32
|
-
export function
|
|
33
|
-
|
|
34
|
-
audience: string,
|
|
35
|
-
visibility: string | null,
|
|
36
|
-
): boolean {
|
|
37
|
-
if (model === null) return true;
|
|
38
|
-
const value = visibility === null || visibility === "" ? model.defaultVisibility : visibility;
|
|
39
|
-
const rank = model.audiences.indexOf(value);
|
|
40
|
-
// An undeclared visibility is refused, never published: a value no build
|
|
41
|
-
// understands is a typo, and a typo reads as a restriction.
|
|
42
|
-
if (rank === -1) return false;
|
|
43
|
-
return rank <= model.audiences.indexOf(audience);
|
|
36
|
+
export function mayReach(source: readonly string[], target: readonly string[]): boolean {
|
|
37
|
+
return target.includes("public") || source.every((a) => target.includes(a));
|
|
44
38
|
}
|
|
@@ -1,168 +1,64 @@
|
|
|
1
|
-
import { decideVisible, type AudienceModel } from "./audience-rule";
|
|
2
|
-
import { instanceFrontmatter } from "./shared";
|
|
3
|
-
|
|
4
1
|
/**
|
|
5
|
-
* The
|
|
6
|
-
*
|
|
2
|
+
* The viewer this build publishes for (record spec §2.4, build spec §3).
|
|
3
|
+
*
|
|
4
|
+
* A concept holds a LIST of audience identifiers; a viewer holds a list that
|
|
5
|
+
* always includes `public`; the concept is visible when the two overlap. The
|
|
6
|
+
* rule itself is `overlaps` in `./audience-rule` — the kernel's copy, asserted
|
|
7
|
+
* byte-identical — and this module only reads the viewer from the environment:
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
* - public
|
|
10
|
-
* - internal
|
|
11
|
-
* - restricted
|
|
12
|
-
* default_visibility: public
|
|
9
|
+
* KSOR_AUDIENCE=public,internal pnpm build
|
|
13
10
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* the behaviour of every instance written before this key existed.
|
|
11
|
+
* Unset means `[public]`, the only default that cannot leak. Whether each
|
|
12
|
+
* identifier is REGISTERED is the staging's question, because the registry
|
|
13
|
+
* lives in the policy (or the lock) and this module reads neither.
|
|
18
14
|
*/
|
|
19
|
-
export type { AudienceModel };
|
|
20
|
-
export { decideVisible };
|
|
21
|
-
|
|
22
|
-
function unquote(raw: string): string {
|
|
23
|
-
const trimmed = raw.trim();
|
|
24
|
-
return /^(['"])(.*)\1$/.exec(trimmed)?.[2] ?? trimmed;
|
|
25
|
-
}
|
|
26
15
|
|
|
27
|
-
/** Every refusal
|
|
16
|
+
/** Every refusal the site makes: a slug a pipeline can match, then the remedy. */
|
|
28
17
|
export function refuse(slug: string, what: string, why: string, fix: string): never {
|
|
29
18
|
// The slug leads, so a pipeline can match on it, and the three lines below
|
|
30
19
|
// it are the whole remedy — an operator never has to read this file.
|
|
31
20
|
throw new Error(`${slug}: ${what}\n why: ${why}\n fix: ${fix}`);
|
|
32
21
|
}
|
|
33
22
|
|
|
34
|
-
|
|
35
|
-
const block = instanceFrontmatter();
|
|
36
|
-
// Top-level key only: `^` under /m cannot match an indented child.
|
|
37
|
-
if (!/^audiences:/m.test(block)) return null;
|
|
38
|
-
|
|
39
|
-
// The grammar mirrors the checker's exactly — CRLF-tolerant, list items at
|
|
40
|
-
// ANY indent (YAML allows unindented block sequences), and a ` #` comment
|
|
41
|
-
// ends an unquoted entry (all three found live 2026-08-18: records the
|
|
42
|
-
// checker blessed either failed this build or silently lost a tier).
|
|
43
|
-
const stripComment = (value: string): string =>
|
|
44
|
-
/^["']/.test(value.trim()) ? value : value.replace(/\s+#.*$/, "");
|
|
45
|
-
// A line scanner, not a block regex: a blank line among the items or a
|
|
46
|
-
// comment on the key line broke the block capture and refused every build
|
|
47
|
-
// of a checker-green record (review finding, 2026-08-19).
|
|
48
|
-
const flow = /^audiences:[ \t]*\[(.*)\][ \t]*(?:#.*)?$/m.exec(block)?.[1];
|
|
49
|
-
let items: string[] = [];
|
|
50
|
-
if (flow !== undefined) {
|
|
51
|
-
items = flow.split(",");
|
|
52
|
-
} else {
|
|
53
|
-
const lines = block.split("\n");
|
|
54
|
-
const start = lines.findIndex((line) => /^audiences:[ \t]*(?:#.*)?$/.test(line));
|
|
55
|
-
if (start !== -1) {
|
|
56
|
-
for (const line of lines.slice(start + 1)) {
|
|
57
|
-
if (line.trim() === "") continue;
|
|
58
|
-
const item = /^[ \t]*-[ \t]+(.*)$/.exec(line);
|
|
59
|
-
if (item === null) break;
|
|
60
|
-
items.push(item[1] ?? "");
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
const audiences = items
|
|
65
|
-
.map(stripComment)
|
|
66
|
-
.map(unquote)
|
|
67
|
-
.filter((value) => value !== "");
|
|
68
|
-
|
|
69
|
-
// A declared-but-unreadable model must never read as "no model": that is
|
|
70
|
-
// the one parse failure that publishes the whole record.
|
|
71
|
-
if (audiences.length === 0) {
|
|
72
|
-
refuse(
|
|
73
|
-
"ksor-audiences-unreadable",
|
|
74
|
-
"instance.md declares `audiences:` but no audience could be read from it",
|
|
75
|
-
"an unreadable model reads as no model, and no model publishes every document — the one parse failure that leaks",
|
|
76
|
-
"write the audiences as a list, least-restricted first:\n audiences:\n - public\n - internal",
|
|
77
|
-
);
|
|
78
|
-
}
|
|
23
|
+
let resolved: readonly string[] | null = null;
|
|
79
24
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
if (new Set(audiences).size !== audiences.length) {
|
|
92
|
-
refuse(
|
|
93
|
-
"ksor-audiences-duplicate",
|
|
94
|
-
`audiences: declares a tier twice (${audiences.join(", ")})`,
|
|
95
|
-
"a duplicated tier has two positions in the ordering, and which one a build honours is undefined",
|
|
96
|
-
"remove the duplicate entry",
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
const defaultVisibility = unquote(
|
|
100
|
-
stripComment(/^default_visibility:[ \t]*(.*)$/m.exec(block)?.[1] ?? ""),
|
|
101
|
-
);
|
|
102
|
-
if (defaultVisibility === "") {
|
|
103
|
-
refuse(
|
|
104
|
-
"ksor-default-visibility-missing",
|
|
105
|
-
"instance.md declares `audiences:` without `default_visibility:`",
|
|
106
|
-
"there is no safe guess: assuming the widest tier leaks on the first document that forgets the key, assuming the narrowest hides the record",
|
|
107
|
-
`add the tier a document without a visibility: key belongs to, e.g. default_visibility: ${audiences[0]}`,
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
if (!audiences.includes(defaultVisibility)) {
|
|
111
|
-
refuse(
|
|
112
|
-
"ksor-default-visibility-undeclared",
|
|
113
|
-
`default_visibility: ${defaultVisibility} is not one of the declared audiences (${audiences.join(", ")})`,
|
|
114
|
-
"every document without a visibility: key belongs to this tier — a tier no build understands is a record no build can publish honestly",
|
|
115
|
-
`set default_visibility: to one of ${audiences.join(", ")}, or declare ${defaultVisibility} in audiences:`,
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return { audiences, defaultVisibility };
|
|
25
|
+
/**
|
|
26
|
+
* The viewer list, `public` always among it. A function rather than a
|
|
27
|
+
* module constant so the refusal is raised where staging can clean up after
|
|
28
|
+
* it: thrown at import time it escaped the stage lock and left the previous,
|
|
29
|
+
* wider stage on disk (found by the staging suite, 2026-08-25).
|
|
30
|
+
*/
|
|
31
|
+
export function viewer(): readonly string[] {
|
|
32
|
+
if (resolved === null) resolved = readViewer();
|
|
33
|
+
return resolved;
|
|
120
34
|
}
|
|
121
35
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
"ksor-audiences-not-declared",
|
|
131
|
-
`KSOR_AUDIENCE="${requested}" was requested, but instance.md declares no audiences`,
|
|
132
|
-
"this build would publish every document — a build that cannot filter must never look like one that did",
|
|
133
|
-
"declare the model in instance.md (audiences: + default_visibility:), or build without KSOR_AUDIENCE",
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
return "";
|
|
137
|
-
}
|
|
138
|
-
// Unset means the least-restricted tier: the only default that cannot leak,
|
|
139
|
-
// so `pnpm build` keeps publishing the public site out of the box.
|
|
140
|
-
if (requested === "") return model.audiences[0] as string;
|
|
141
|
-
if (!model.audiences.includes(requested)) {
|
|
36
|
+
function readViewer(): readonly string[] {
|
|
37
|
+
const raw = process.env.KSOR_AUDIENCE?.trim() ?? "";
|
|
38
|
+
if (raw === "") return ["public"];
|
|
39
|
+
const list = raw
|
|
40
|
+
.split(",")
|
|
41
|
+
.map((item) => item.trim())
|
|
42
|
+
.filter((item) => item !== "");
|
|
43
|
+
if (!list.includes("public")) {
|
|
142
44
|
refuse(
|
|
143
|
-
"ksor-
|
|
144
|
-
`KSOR_AUDIENCE="${
|
|
145
|
-
"
|
|
146
|
-
`build with
|
|
45
|
+
"ksor-viewer-omits-public",
|
|
46
|
+
`KSOR_AUDIENCE="${raw}" does not include public`,
|
|
47
|
+
"a viewer list always includes public — every reader of a restricted build is also a reader of the open one, and a build for a restricted audience alone would silently drop every public concept",
|
|
48
|
+
`build with KSOR_AUDIENCE=public,${list.join(",")}`,
|
|
147
49
|
);
|
|
148
50
|
}
|
|
149
|
-
return
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/** The audience this build publishes for; "" when the record has no model. */
|
|
153
|
-
export const buildAudience: string = resolveBuildAudience(audienceModel);
|
|
154
|
-
|
|
155
|
-
/** Whether a document of this visibility belongs in THIS build. */
|
|
156
|
-
export function visibleInBuild(visibility: string | null): boolean {
|
|
157
|
-
return decideVisible(audienceModel, buildAudience, visibility);
|
|
51
|
+
return [...new Set(list)];
|
|
158
52
|
}
|
|
159
53
|
|
|
160
54
|
/**
|
|
161
|
-
* What a
|
|
162
|
-
* screenshot of an internal site says which
|
|
163
|
-
* public build
|
|
55
|
+
* What a restricted build calls itself, in the site chrome — so a leaked
|
|
56
|
+
* screenshot of an internal site says which audiences it was built for. The
|
|
57
|
+
* public build says nothing new.
|
|
164
58
|
*/
|
|
165
|
-
export
|
|
166
|
-
|
|
59
|
+
export function audienceNotice(): string | null {
|
|
60
|
+
const list = viewer();
|
|
61
|
+
return list.length === 1
|
|
167
62
|
? null
|
|
168
|
-
: `${
|
|
63
|
+
: `${list.filter((a) => a !== "public").join(", ")} build — not for publication`;
|
|
64
|
+
}
|
|
@@ -69,6 +69,15 @@ export function hostOf(value: string): string | null {
|
|
|
69
69
|
* per document, named freely, staged only when a published document links to
|
|
70
70
|
* it. It is deliberately NOT an attachment — an attachment is named after its
|
|
71
71
|
* parent (`<doc>.quiz.yaml`), and seven sims cannot all be `index.sim.html`.
|
|
72
|
+
*
|
|
73
|
+
* CANONICAL in `packages/content/src/lib/sim-rule.ts`, because the record's
|
|
74
|
+
* checker decides which files are admitted at all and this file only decides
|
|
75
|
+
* which links become a frame — one rule, two readers. The definition is
|
|
76
|
+
* repeated here rather than imported to keep this file a LEAF (above), which
|
|
77
|
+
* is not a preference: a relative import here needs `./sim-rule.js` under the
|
|
78
|
+
* package's node16 typecheck and `./sim-rule` under the site's bundler, and
|
|
79
|
+
* no one spelling satisfies both. So the two copies are PINNED to each other
|
|
80
|
+
* in `embed-rule.test.ts` instead — decision 18's shape, on a smaller rule.
|
|
72
81
|
*/
|
|
73
82
|
export const SIM_SUFFIX = ".sim.html";
|
|
74
83
|
|