@panaversity/ksor 0.0.39 → 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.
Files changed (108) hide show
  1. package/CHANGELOG.md +965 -0
  2. package/README.md +29 -9
  3. package/dist/checker/check-main.mjs +14049 -0
  4. package/dist/cli.mjs +11528 -5213
  5. package/dist/gateway-api-CF4ED9_g-BQusM_dK.mjs +10895 -0
  6. package/dist/gateway.d.mts +52 -13
  7. package/dist/gateway.mjs +2 -2
  8. package/dist/index.d.mts +1 -1
  9. package/dist/index.mjs +1 -1
  10. package/dist/{src-pl4aOpVs.mjs → src-dqpI-p1a.mjs} +1 -0
  11. package/docs/authorization.md +8 -6
  12. package/docs/deploying.md +36 -25
  13. package/docs/index.md +26 -13
  14. package/docs/ingesting.md +70 -22
  15. package/docs/tool-surface.md +69 -16
  16. package/package.json +4 -3
  17. package/schema/migrations/2.4-2.5__okf-profile.sql +114 -0
  18. package/schema/schema.sql +77 -14
  19. package/templates/scaffold/.agents/skills/add-sources/SKILL.md +63 -18
  20. package/templates/scaffold/.agents/skills/format-checker/SKILL.md +42 -33
  21. package/templates/scaffold/.agents/skills/format-checker/check.mjs +13827 -1314
  22. package/templates/scaffold/.agents/skills/intake-interview/SKILL.md +65 -27
  23. package/templates/scaffold/.agents/skills/make-slides/SKILL.md +7 -5
  24. package/templates/scaffold/.agents/skills/make-summary/SKILL.md +13 -6
  25. package/templates/scaffold/.claude/skills/add-sources/SKILL.md +63 -18
  26. package/templates/scaffold/.claude/skills/format-checker/SKILL.md +42 -33
  27. package/templates/scaffold/.claude/skills/format-checker/check.mjs +13827 -1314
  28. package/templates/scaffold/.claude/skills/intake-interview/SKILL.md +65 -27
  29. package/templates/scaffold/.claude/skills/make-slides/SKILL.md +7 -5
  30. package/templates/scaffold/.claude/skills/make-summary/SKILL.md +13 -6
  31. package/templates/scaffold/.github/workflows/validate.yml +9 -1
  32. package/templates/scaffold/.ksor/governance.yaml +17 -0
  33. package/templates/scaffold/AGENTS.md +293 -119
  34. package/templates/scaffold/Dockerfile +5 -1
  35. package/templates/scaffold/README.md +160 -42
  36. package/templates/scaffold/env.example +37 -6
  37. package/templates/scaffold/gitignore +14 -6
  38. package/templates/scaffold/instance.md +21 -17
  39. package/templates/scaffold/knowledge/governance-ladder.md +11 -2
  40. package/templates/scaffold/knowledge/index.md +9 -0
  41. package/templates/scaffold/knowledge/surfaces/for-agents.md +13 -6
  42. package/templates/scaffold/knowledge/surfaces/for-people.md +7 -6
  43. package/templates/scaffold/knowledge/surfaces/index.md +4 -20
  44. package/templates/scaffold/knowledge/surfaces/overview.md +25 -0
  45. package/templates/scaffold/knowledge/what-is-a-ksor.md +6 -5
  46. package/templates/scaffold/knowledge/what-is-a-ksor.summary.md +4 -0
  47. package/templates/scaffold/package.json +3 -4
  48. package/templates/scaffold/pnpm-lock.yaml +3 -0
  49. package/templates/scaffold/system/gateways/content.ts +13 -0
  50. package/templates/scaffold/system/site/app/(home)/page.tsx +2 -2
  51. package/templates/scaffold/system/site/app/.well-known/mcp/server.json/route.ts +10 -0
  52. package/templates/scaffold/system/site/app/docs/[[...slug]]/page.tsx +134 -91
  53. package/templates/scaffold/system/site/app/global.css +198 -6
  54. package/templates/scaffold/system/site/app/layout.tsx +8 -3
  55. package/templates/scaffold/system/site/app/llms-full.txt/route.ts +13 -7
  56. package/templates/scaffold/system/site/app/llms.txt/route.ts +12 -7
  57. package/templates/scaffold/system/site/app/md/[[...slug]]/route.ts +26 -25
  58. package/templates/scaffold/system/site/components/code-block.tsx +87 -0
  59. package/templates/scaffold/system/site/components/embed.tsx +276 -0
  60. package/templates/scaffold/system/site/components/footer-mark.tsx +3 -2
  61. package/templates/scaffold/system/site/components/governance.tsx +219 -87
  62. package/templates/scaffold/system/site/components/mdx.tsx +10 -2
  63. package/templates/scaffold/system/site/components/record-index.tsx +5 -5
  64. package/templates/scaffold/system/site/components/record-stack.tsx +10 -9
  65. package/templates/scaffold/system/site/components/record-views.tsx +6 -17
  66. package/templates/scaffold/system/site/components/sidebar-status.tsx +19 -18
  67. package/templates/scaffold/system/site/lib/alert-rule.ts +214 -0
  68. package/templates/scaffold/system/site/lib/attachment-rule.ts +6 -1
  69. package/templates/scaffold/system/site/lib/attachments.ts +0 -28
  70. package/templates/scaffold/system/site/lib/audience-rule.ts +15 -21
  71. package/templates/scaffold/system/site/lib/audience.ts +42 -146
  72. package/templates/scaffold/system/site/lib/embed-rule.ts +255 -0
  73. package/templates/scaffold/system/site/lib/governance.ts +339 -225
  74. package/templates/scaffold/system/site/lib/index-routes.ts +125 -0
  75. package/templates/scaffold/system/site/lib/lifecycle-rule.ts +52 -0
  76. package/templates/scaffold/system/site/lib/lock.ts +282 -0
  77. package/templates/scaffold/system/site/lib/order-rule.ts +37 -0
  78. package/templates/scaffold/system/site/lib/record-href.ts +68 -0
  79. package/templates/scaffold/system/site/lib/record-link.tsx +26 -0
  80. package/templates/scaffold/system/site/lib/rules-version.ts +11 -0
  81. package/templates/scaffold/system/site/lib/shared.ts +67 -104
  82. package/templates/scaffold/system/site/lib/sim-rule.ts +49 -0
  83. package/templates/scaffold/system/site/lib/source.ts +256 -186
  84. package/templates/scaffold/system/site/lib/stage-knowledge.ts +638 -467
  85. package/templates/scaffold/system/site/lib/stage-manifest.ts +128 -0
  86. package/templates/scaffold/system/site/package.json +1 -0
  87. package/templates/scaffold/system/site/record/actor.ts +23 -0
  88. package/templates/scaffold/system/site/record/check.ts +571 -0
  89. package/templates/scaffold/system/site/record/citations.ts +312 -0
  90. package/templates/scaffold/system/site/record/frontmatter.ts +134 -0
  91. package/templates/scaffold/system/site/record/git-ledger.ts +171 -0
  92. package/templates/scaffold/system/site/record/hygiene.ts +320 -0
  93. package/templates/scaffold/system/site/record/index-file.ts +150 -0
  94. package/templates/scaffold/system/site/record/index.ts +103 -0
  95. package/templates/scaffold/system/site/record/instance.ts +257 -0
  96. package/templates/scaffold/system/site/record/instant.ts +43 -0
  97. package/templates/scaffold/system/site/record/ledger.ts +694 -0
  98. package/templates/scaffold/system/site/record/load.ts +129 -0
  99. package/templates/scaffold/system/site/record/lock.ts +306 -0
  100. package/templates/scaffold/system/site/record/near-miss.ts +37 -0
  101. package/templates/scaffold/system/site/record/policy.ts +414 -0
  102. package/templates/scaffold/system/site/record/profile.ts +535 -0
  103. package/templates/scaffold/system/site/record/refusal.ts +106 -0
  104. package/templates/scaffold/system/site/record/yaml-file.ts +103 -0
  105. package/templates/scaffold/system/site/source.config.ts +109 -23
  106. package/dist/gateway-api-CmIthmJS-IUA9qS-T.mjs +0 -3225
  107. package/templates/scaffold/system/site/lib/denial-rule.ts +0 -220
  108. package/templates/scaffold/system/site/lib/page-order.ts +0 -93
@@ -0,0 +1,103 @@
1
+ /**
2
+ * One reader for the control files beside the bundle (`.ksor/*.yaml`): the
3
+ * same parser posture as a concept's frontmatter — one document, unique keys,
4
+ * plain data throughout, a mapping at the root — refused under the file's own
5
+ * slug.
6
+ *
7
+ * Every clause of that posture was re-probed against `yaml` 2.9.0 on
8
+ * 2026-08-25, because the previous wording listed `schema: "core"` among them
9
+ * as though it were a safety property. What the options really buy:
10
+ *
11
+ * - `uniqueKeys` makes a repeated key an ERROR, so a second `takedown_authorities:`
12
+ * cannot quietly win over the first.
13
+ * - An unknown tag (`!Foo`) is an error, so nothing resolves through a handler
14
+ * this codebase never wrote.
15
+ * - An alias bomb throws at `toJS()` ("Excessive alias count"), which the
16
+ * `catch` turns into a refusal rather than a hung process.
17
+ * - `schema: "core"` decides how PLAIN scalars resolve — `.inf` is a number,
18
+ * `2026-01-01` stays a string — and that is all it decides. It does NOT
19
+ * refuse the YAML 1.1 type tags: `!!binary` resolves to a Buffer, `!!set` to
20
+ * a Set and `!!omap` to a Map, with no error and no warning. The root-only
21
+ * mapping check never looked at a value, so a control file could carry an
22
+ * object no rule here is written against — `Policy.raw` is published
23
+ * verbatim — which is why `firstNonPlain` walks the whole document.
24
+ * - `__proto__` needs no defence and gets none: `yaml` assigns through
25
+ * `Object.defineProperty`, so it lands as an ordinary own key and the global
26
+ * prototype is untouched. Pinned in the tests, not asserted here.
27
+ */
28
+ import { parseAllDocuments } from "yaml";
29
+
30
+ import type { Refusal, RefusalSlug } from "./refusal";
31
+
32
+ export type YamlFileResult =
33
+ | { readonly ok: true; readonly value: Readonly<Record<string, unknown>> }
34
+ | { readonly ok: false; readonly refusals: readonly Refusal[] };
35
+
36
+ const SHAPE_FIX = "fix the YAML so the file is one mapping of the keys the spec names";
37
+
38
+ export function parseYamlFile(text: string, path: string, slug: RefusalSlug): YamlFileResult {
39
+ const refuse = (why: string, fix: string = SHAPE_FIX): YamlFileResult => ({
40
+ ok: false,
41
+ refusals: [{ slug, path, why, fix }],
42
+ });
43
+ let value: unknown;
44
+ try {
45
+ const docs = parseAllDocuments(text.replace(/^\uFEFF/, ""), {
46
+ schema: "core",
47
+ uniqueKeys: true,
48
+ logLevel: "silent",
49
+ });
50
+ if (docs.length > 1) return refuse("the file holds more than one YAML document");
51
+ const problem = docs[0]?.errors[0] ?? docs[0]?.warnings[0];
52
+ if (problem !== undefined) {
53
+ return refuse(`not valid YAML: ${problem.message.split("\n")[0] ?? ""}`);
54
+ }
55
+ value = docs[0]?.toJS();
56
+ } catch (error) {
57
+ const first = String(error).split("\n")[0] ?? "";
58
+ return refuse(`not valid YAML: ${first.replace(/^\w*Error: /, "")}`);
59
+ }
60
+ if (value === null || value === undefined) value = {};
61
+ if (!isPlainMapping(value)) return refuse("the file is not a mapping at its root");
62
+ const tagged = firstNonPlain(value, "");
63
+ if (tagged !== null) {
64
+ return refuse(
65
+ `the value at \`${tagged}\` is not plain data — a \`!!tag\` turned it into something no reader expects`,
66
+ "write strings, numbers, booleans, lists and mappings only; timestamps stay quoted strings",
67
+ );
68
+ }
69
+ return { ok: true, value };
70
+ }
71
+
72
+ export function isPlainMapping(value: unknown): value is Record<string, unknown> {
73
+ if (typeof value !== "object" || value === null) return false;
74
+ const proto: unknown = Object.getPrototypeOf(value);
75
+ return proto === Object.prototype || proto === null;
76
+ }
77
+
78
+ /**
79
+ * Path of the first value that is neither a scalar, an array nor a plain
80
+ * mapping — `null` when all are. One walk, shared with `splitFrontmatter`: the
81
+ * frontmatter reader grew it first, and a control file that decides authority
82
+ * and denial is the last place to enforce a weaker rule than a document's own
83
+ * header does.
84
+ */
85
+ export function firstNonPlain(value: unknown, at: string): string | null {
86
+ if (value === null) return null;
87
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
88
+ return null;
89
+ }
90
+ if (Array.isArray(value)) {
91
+ for (const [i, item] of value.entries()) {
92
+ const hit = firstNonPlain(item, `${at}[${i}]`);
93
+ if (hit !== null) return hit;
94
+ }
95
+ return null;
96
+ }
97
+ if (!isPlainMapping(value)) return at === "" ? "(root)" : at;
98
+ for (const [key, item] of Object.entries(value)) {
99
+ const hit = firstNonPlain(item, at === "" ? key : `${at}.${key}`);
100
+ if (hit !== null) return hit;
101
+ }
102
+ return null;
103
+ }
@@ -1,5 +1,10 @@
1
+ import path from "node:path";
2
+
1
3
  import { defineCollections, defineConfig, defineDocs } from "fumadocs-mdx/config";
4
+ import { rehypeCodeDefaultOptions } from "fumadocs-core/mdx-plugins";
2
5
  import { remarkCodeTab } from "fumadocs-core/mdx-plugins/remark-code-tab";
6
+ import { rehypeGithubAlerts } from "./lib/alert-rule";
7
+ import { rehypeEmbeds } from "./lib/embed-rule";
3
8
  import { metaSchema, pageSchema } from "fumadocs-core/source/schema";
4
9
  import { z } from "zod";
5
10
  import { DeckSchema } from "./lib/deck";
@@ -7,40 +12,50 @@ import { QuizSchema } from "./lib/quiz";
7
12
  import { SlidesSchema } from "./lib/slides";
8
13
  import { isAttachment } from "./lib/attachment-rule";
9
14
  import { rehypeTeachingAid } from "./lib/teaching-aid-rule";
15
+ import { STAGE_DIR } from "./lib/stage-manifest";
10
16
  import { knowledgeSourceDir } from "./lib/stage-knowledge";
11
17
 
12
- // The record lives at <repo>/knowledge — two levels up from this site.
13
- // Governance frontmatter (status, owner, provenance, superseded_by) is
14
- // tolerated on top of the default page schema so a governed document
15
- // always renders; `pnpm check` at the repo root is what enforces it.
18
+ // The record lives at <repo>/knowledge — two levels up from this site. The
19
+ // profile's governance frontmatter (record spec §2) is tolerated on top of
20
+ // the default page schema so a governed document always renders; the record
21
+ // checker, which staging runs, is what enforces it.
16
22
  //
17
- // When instance.md declares `audiences:`, the documents this build may
18
- // publish (and the assets they reference) are staged into a filtered copy
19
- // FIRST, and this is where that copy is chosen: one directory, one filter,
20
- // every surface downstream. See lib/stage-knowledge.ts.
23
+ // The documents this build's viewer may see (and the assets they reference)
24
+ // are staged into a filtered copy FIRST, for EVERY build, and this is where
25
+ // that copy is chosen: one directory, one filter, every surface downstream.
26
+ // See lib/stage-knowledge.ts.
21
27
  export const docs = defineDocs({
22
28
  dir: knowledgeSourceDir(),
23
29
  docs: {
24
- // ONE exclusion, and it is the whole of "an attachment is not a document".
25
- // The route table, the sidebar, llms.txt, llms-full.txt, /md/, the search
26
- // index and the caveat map ALL read `source`, and `source` reads exactly
27
- // this collection so subtracting attachments here subtracts them from
28
- // every surface at once. Doing it per-surface instead is the failure mode
29
- // research/visibility.md §4-§5 is cited for; pruning the page tree is not
30
- // even sufficient, because getSortedPages() deliberately re-adds what the
31
- // tree dropped and the search index never consults the tree.
32
- files: ["**/*.md", "**/*.mdx", "!**/*.summary.md", "!**/*.summary.mdx"],
30
+ // TWO exclusions, and they are the whole of "an attachment is not a
31
+ // document" and "an index is not a document". The route table, the
32
+ // sidebar, llms.txt, llms-full.txt, /md/, the search index and the badge
33
+ // map ALL read `source`, and `source` reads exactly this collection — so
34
+ // subtracting here subtracts from every surface at once. Doing it
35
+ // per-surface is the failure mode research/visibility.md §4-§5 is cited
36
+ // for; pruning the page tree is not even sufficient, because the search
37
+ // index never consults the tree. The regenerated `index.md` is rendered by
38
+ // the folder page component instead (record spec §1: no route, no twin,
39
+ // no llms.txt line — it carries no governance to publish under).
40
+ // `.md` only: the record is CommonMark (record spec §1), the checker
41
+ // refuses an `.mdx` under knowledge/ by name (`ksor-file-type`), and
42
+ // staging copies nothing else — so an `.mdx` pattern here advertised a
43
+ // shape the stage can never contain.
44
+ files: ["**/*.md", "!**/*.summary.md", "!**/index.md"],
33
45
  schema: pageSchema
34
46
  .extend({
47
+ type: z.string().optional(),
35
48
  status: z.string().optional(),
36
- owner: z.string().optional(),
37
- provenance: z.array(z.string()).optional(),
38
- superseded_by: z.string().optional(),
49
+ order: z.number().optional(),
50
+ ksor: z.record(z.string(), z.any()).optional(),
39
51
  })
40
52
  .catchall(z.any()),
41
53
  postprocess: {
42
- // Exposes each page's processed markdown llms.txt/llms-full.txt
43
- // depend on it.
54
+ // Exposes each page's processed markdown, for the page's reading time.
55
+ // NOT for the machine surfaces: `/md/` and `llms-full.txt` republish the
56
+ // STAGED bytes, because the processed markdown is the mdast serialized
57
+ // after every remark plugin has run and carries this shell's rendering
58
+ // rather than the record's grammar (lib/stage-manifest.ts, `stagedBody`).
44
59
  includeProcessedMarkdown: true,
45
60
  },
46
61
  },
@@ -117,13 +132,84 @@ export const slides = defineCollections({
117
132
 
118
133
  export default defineConfig({
119
134
  mdxOptions: {
135
+ /**
136
+ * A bundle-absolute image (`![chart](/chart.png)`) resolves against the
137
+ * STAGE, which is where `/`-rooted links resolve everywhere else in the
138
+ * record.
139
+ *
140
+ * OKF §6.1 allows both link forms and record spec §2.3 says the record
141
+ * carries both, so the checker accepts `/chart.png` and staging copies the
142
+ * file to `.staged-knowledge/chart.png`. fumadocs' `remarkImage` is applied
143
+ * by default and resolves a `/`-rooted src against `<cwd>/public` — a
144
+ * directory this scaffold does not have — so it emitted
145
+ * `import __img0 from "../../public/chart.png"` and the export died with
146
+ * "Module not found" naming a path that exists nowhere in the record. Two
147
+ * halves of one pipeline resolving the same string against two roots; this
148
+ * gives them one root. Absolute, because `remarkImage` joins it with the
149
+ * src and then takes a path RELATIVE to the document's directory.
150
+ */
151
+ remarkImageOptions: { publicDir: path.resolve(process.cwd(), STAGE_DIR) },
152
+ /**
153
+ * A fence in a language the highlighter does not carry renders as PLAIN
154
+ * TEXT rather than failing the build.
155
+ *
156
+ * A record is not a code project: an author writing ```promql, ```logql or
157
+ * ```gotemplate is describing their own stack, and shiki throws on a
158
+ * language it has no grammar for. Without this, one fence anywhere in the
159
+ * record takes the whole site down with a stack trace naming a file in
160
+ * node_modules — found live on a real 187-document handbook, where three
161
+ * such languages appeared across 3,000 fences.
162
+ *
163
+ * `lazy` so only the grammars a record actually uses are loaded, which is
164
+ * also what keeps that first unknown fence from being fatal.
165
+ *
166
+ * The defaults are SPREAD, not replaced: `RehypeCodeOptions` is a full
167
+ * options object (it requires `themes`), so passing the two keys alone
168
+ * type-errors on a manager that resolves fumadocs fresh rather than from
169
+ * the pinned lockfile — found in CI on the npm and bun scaffolds, which
170
+ * ship no lockfile by design (decision 25).
171
+ */
172
+ rehypeCodeOptions: {
173
+ ...rehypeCodeDefaultOptions,
174
+ lazy: true,
175
+ fallbackLanguage: "text",
176
+ },
120
177
  /**
121
178
  * WHERE the teaching aid sits: after the document's introduction, which
122
179
  * is everything before its first `##` section. The plugin only marks the
123
180
  * place; the page decides whether there is a deck to put there. See
124
181
  * lib/teaching-aid-rule.ts.
125
182
  */
126
- rehypePlugins: [[rehypeTeachingAid, { isAttachment }]],
183
+ rehypePlugins: [
184
+ [rehypeTeachingAid, { isAttachment }],
185
+ /**
186
+ * A passage the reader must not miss, as a CALLOUT.
187
+ *
188
+ * GitHub's alert syntax: a blockquote whose first line is `[!WARNING]`.
189
+ * GitHub renders it as a styled alert, every other viewer renders an
190
+ * ordinary blockquote carrying a visible label, and it reaches `/md/`
191
+ * and `llms-full.txt` as the author's blockquote rather than as markup.
192
+ *
193
+ * Not `:::warning`. fumadocs ships `remarkAdmonition` for that form and
194
+ * deprecates it in favour of a `remark-directive` setup — but the deeper
195
+ * objection is that `:::` is a dialect, and a record written in one
196
+ * renders as literal punctuation everywhere it is read outside this site.
197
+ *
198
+ * REHYPE rather than remark, and lib/alert-rule.ts records why: the
199
+ * record's markdown is serialized from the mdast, so converting there
200
+ * publishes this site's React component to the agent surface.
201
+ */
202
+ rehypeGithubAlerts,
203
+ /**
204
+ * An interactive page the document points at, as a click-to-load
205
+ * frame. Authored as an ordinary link titled `embed`, so the record
206
+ * stays CommonMark and every other reader of it sees a link.
207
+ *
208
+ * REHYPE, so `/md/` and `llms-full.txt` keep the author's link
209
+ * rather than this site's component. See lib/embed-rule.ts.
210
+ */
211
+ rehypeEmbeds,
212
+ ],
127
213
  /**
128
214
  * Alternative versions of the same instruction, as TABS.
129
215
  *