@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,276 @@
1
+ "use client";
2
+
3
+ import { ExternalLink, Play } from "lucide-react";
4
+ import { useCallback, useEffect, useRef, useState, type ReactElement } from "react";
5
+
6
+ import { Button } from "@/components/ui/button";
7
+
8
+ /**
9
+ * An interactive page the document points at.
10
+ *
11
+ * Authored as an ordinary link (see lib/embed-rule.ts); rendered here as a
12
+ * frame the reader loads by asking. The click is not politeness — the
13
+ * scaffold's browser test asserts ZERO external requests on a built page, and
14
+ * that guarantee is what makes this record work offline, behind a firewall,
15
+ * and without telling a third party which document someone is reading. An
16
+ * always-on frame would break it on every page carrying one.
17
+ *
18
+ * Two things the panel says out loud, because they are the reason for the
19
+ * click. It NAMES the host, so a reader consents to a party rather than to a
20
+ * button. And it says the page is not part of the record: an embed carries no
21
+ * provenance claim, cannot be cited, and can change under the document without
22
+ * anyone reviewing it. The link out is always there and costs nothing, because
23
+ * a plain `<a>` is not a request.
24
+ */
25
+
26
+ /**
27
+ * A framed page may not scroll the document that hosts it.
28
+ *
29
+ * `scrollIntoView` scrolls EVERY ancestor scrolling box, and a frame's
30
+ * ancestors include the host page — so a page that auto-scrolls its own log
31
+ * throws the reader somewhere else entirely. Six of the seven sims this was
32
+ * built for do exactly that, and clicking one moved the page 5,807px (found
33
+ * live).
34
+ *
35
+ * The call still does its real job inside the frame; only the part that moved
36
+ * the host is undone, in the same task, so nothing is painted in between.
37
+ *
38
+ * Same-origin only, which is all it can be: a cross-origin frame's prototypes
39
+ * are not reachable, and it cannot scroll us either.
40
+ */
41
+ function containScrolling(doc: Document | null): void {
42
+ const view = doc?.defaultView;
43
+ if (!view) return;
44
+ const proto = view.Element.prototype as Element & {
45
+ scrollIntoView: (...args: unknown[]) => void;
46
+ __ksorContained?: boolean;
47
+ };
48
+ if (proto.__ksorContained) return;
49
+ const native = proto.scrollIntoView;
50
+ proto.scrollIntoView = function contained(this: Element, ...args: unknown[]): void {
51
+ const { scrollX, scrollY } = window;
52
+ native.apply(this, args);
53
+ if (window.scrollX !== scrollX || window.scrollY !== scrollY) {
54
+ window.scrollTo(scrollX, scrollY);
55
+ }
56
+ };
57
+ proto.__ksorContained = true;
58
+ }
59
+
60
+ /**
61
+ * Close the gutter a framed page leaves around itself.
62
+ *
63
+ * A page that does not reset `body { margin }` keeps the user agent's 8px, and
64
+ * that band shows whatever is behind the frame — so a sim painted near-black
65
+ * sat inside a ring of near-white. Matching the colour instead was tried and
66
+ * does not work: these pages paint below the body, so `html`, `body` and even
67
+ * the wrapper all compute to transparent (measured on the one that has the
68
+ * gutter), and chasing the colour down the tree would be guesswork.
69
+ *
70
+ * Zeroing it is the honest fix, and it is what six of the seven sims already
71
+ * do in their own stylesheet — this only makes the seventh agree. Presentation
72
+ * of the frame is the host's to decide; the page's content is untouched.
73
+ */
74
+ function closeGutter(doc: Document | null): void {
75
+ const body = doc?.body;
76
+ if (!body) return;
77
+ body.style.margin = "0";
78
+ }
79
+
80
+ export function Embed({
81
+ url,
82
+ host,
83
+ label,
84
+ owned,
85
+ }: {
86
+ readonly url: string;
87
+ readonly host: string;
88
+ readonly label: string;
89
+ /** "true" when the page is carried IN the record and served from here. */
90
+ readonly owned?: string;
91
+ }): ReactElement {
92
+ const [loaded, setLoaded] = useState(false);
93
+ const [height, setHeight] = useState<number | null>(null);
94
+ const frame = useRef<HTMLIFrameElement>(null);
95
+ const isOwned = owned === "true";
96
+
97
+ /**
98
+ * Fit the frame to the page it holds, so nothing scrolls in a box and no
99
+ * band of dead space sits under it.
100
+ *
101
+ * Measure the body's CHILDREN, never `documentElement.scrollHeight`. These
102
+ * pages set `min-height: 100vh`, and inside a frame the viewport IS the
103
+ * frame — so the document's scroll height is just the frame's own height
104
+ * echoed back, and a frame sized from it grows without bound. Watched that
105
+ * run away before the CSS explained it (2026-08-24). The children do not
106
+ * depend on the frame: measured across all seven sims of the record this was
107
+ * built for, each returned the same height at a 300px frame and a 1400px
108
+ * one.
109
+ *
110
+ * Possible at all only because a carried page is SAME-ORIGIN. A cross-origin
111
+ * frame refuses `contentDocument`, so it keeps the ratio box and this
112
+ * returns without touching anything.
113
+ */
114
+ const fit = useCallback((): void => {
115
+ let doc: Document | null = null;
116
+ try {
117
+ doc = frame.current?.contentDocument ?? null;
118
+ } catch {
119
+ return; // cross-origin: not ours to measure
120
+ }
121
+ const body = doc?.body;
122
+ if (!body) return;
123
+ // NOT `instanceof HTMLElement`. The elements inside a frame belong to the
124
+ // frame's realm, so they are instances of ITS HTMLElement and never of this
125
+ // one — the filter matched nothing, and the frame kept the ratio box while
126
+ // looking as though measuring had simply not helped (found live).
127
+ const blocks = [...body.children].filter(
128
+ (el): el is HTMLElement => typeof (el as HTMLElement).offsetHeight === "number",
129
+ );
130
+ if (blocks.length === 0) return;
131
+ const bottom = Math.max(...blocks.map((el) => el.offsetTop + el.offsetHeight));
132
+ // The body's own bottom edge, padding AND margin. A page that does not
133
+ // reset `body { margin }` keeps the user agent's 8px, and leaving the
134
+ // margin out left exactly that much overflowing — one scrollbar, on the
135
+ // one sim of seven whose stylesheet omits the reset (found live).
136
+ // `offsetTop` already carries the top margin.
137
+ const style = getComputedStyle(body);
138
+ const edge =
139
+ (Number.parseFloat(style.paddingBottom) || 0) + (Number.parseFloat(style.marginBottom) || 0);
140
+ const measured = Math.ceil(bottom + edge);
141
+ // GROW-ONLY. A running page changes height every beat — measured
142
+ // oscillating 504 to 562 on one sim — and following it exactly would
143
+ // shift everything below the frame while someone is reading. The high
144
+ // water mark costs a little slack after a shrink and never a scrollbar.
145
+ if (measured > 0)
146
+ setHeight((current) => (current === null ? measured : Math.max(current, measured)));
147
+ }, []);
148
+
149
+ const watcher = useRef<ResizeObserver | null>(null);
150
+
151
+ /**
152
+ * Measure on load, then keep watching — these pages animate, and some add a
153
+ * row per beat.
154
+ *
155
+ * Set up HERE rather than in an effect on `loaded`: that effect runs the
156
+ * moment the click flips the state, which is before the frame's document
157
+ * exists, so it observed an empty `about:blank` and never fired again (found
158
+ * live — the frame fitted once and then ignored everything).
159
+ *
160
+ * Watch the CHILDREN, not the body. The body's own box is pinned by the
161
+ * page's `min-height: 100vh` to exactly the frame, so it never reports a
162
+ * change.
163
+ */
164
+ const handleLoad = useCallback((): void => {
165
+ fit();
166
+ let doc: Document | null = null;
167
+ try {
168
+ doc = frame.current?.contentDocument ?? null;
169
+ } catch {
170
+ return;
171
+ }
172
+ containScrolling(doc);
173
+ closeGutter(doc);
174
+ const body = doc?.body;
175
+ if (!body || typeof ResizeObserver === "undefined") return;
176
+ watcher.current?.disconnect();
177
+ const observer = new ResizeObserver(fit);
178
+ for (const child of body.children) observer.observe(child);
179
+ watcher.current = observer;
180
+ }, [fit]);
181
+
182
+ useEffect(() => () => watcher.current?.disconnect(), []);
183
+
184
+ return (
185
+ <figure
186
+ // The measure, like every other figure in the document. Wider was tried
187
+ // twice — 64rem, then 56rem — and both read as a block that had stopped
188
+ // belonging to the text around it (owner, seen live). These pages are
189
+ // responsive and lay themselves out at whatever width they are given, so
190
+ // the measure costs them nothing.
191
+ className="not-prose my-8 w-full"
192
+ >
193
+ <div
194
+ // A ratio rather than a fixed height, so the frame scales with the
195
+ // measure. The floor is there because these pages are usually taller
196
+ // than they are wide, and a narrow window would otherwise letterbox
197
+ // an interactive thing down to a strip.
198
+ className={
199
+ // Before the click this is a card, and it should read as one. After
200
+ // it, the page inside brings its own surface — and the box's does
201
+ // not match it: a sim painted near-black arrived ringed in
202
+ // near-white, on every side the page did not fill.
203
+ //
204
+ // Fitting the WIDTH to the page was tried and cannot work: these
205
+ // pages are responsive, so narrowing the frame made them reflow
206
+ // narrower still, which left a fresh gap and, on one, a scrollbar
207
+ // (measured). A page that does not fill the width now simply sits on
208
+ // the page, which is the honest arrangement — it is the record's own
209
+ // figure, not a screenshot in a mount.
210
+ loaded
211
+ ? "relative w-full overflow-hidden rounded-lg"
212
+ : "relative w-full overflow-hidden rounded-lg border border-fd-border bg-fd-muted"
213
+ }
214
+ // The invitation is a card. Once the page is measured the height goes
215
+ // on the FRAME instead and this box wraps it: put on the box, the
216
+ // border is inside that height, so the frame came out two pixels short
217
+ // and every single embed carried a scrollbar (found live — content 862
218
+ // in a frame of 860). Until it is measured, and always for a frame we
219
+ // may not measure, a ratio with a floor.
220
+ style={
221
+ !loaded
222
+ ? { height: "14rem" }
223
+ : height === null
224
+ ? { aspectRatio: "16 / 10", minHeight: "26rem" }
225
+ : undefined
226
+ }
227
+ >
228
+ {loaded ? (
229
+ <iframe
230
+ ref={frame}
231
+ onLoad={handleLoad}
232
+ src={url}
233
+ title={label}
234
+ allowFullScreen
235
+ // The host learns that its page was opened, not which document of
236
+ // this record opened it.
237
+ referrerPolicy="no-referrer"
238
+ // Scripts, because the point of the page is that it runs. Not
239
+ // top-navigation, and not forms: a framed page may not steer the
240
+ // reader out of the record or collect anything from inside it.
241
+ sandbox="allow-scripts allow-same-origin allow-popups"
242
+ loading="lazy"
243
+ // Absolute only while the box owns the height, which is the ratio
244
+ // case. With a measured height the frame owns it and lays out in
245
+ // flow, so the box is exactly as tall as the page plus its border.
246
+ className={height === null ? "absolute inset-0 size-full" : "block w-full"}
247
+ style={height === null ? undefined : { height }}
248
+ />
249
+ ) : (
250
+ <div className="absolute inset-0 flex flex-col items-center justify-center gap-4 px-6 text-center">
251
+ <Play aria-hidden className="size-8 text-fd-muted-foreground" />
252
+ <Button onClick={() => setLoaded(true)}>{label}</Button>
253
+ <p className="max-w-sm text-xs text-fd-muted-foreground">
254
+ {isOwned
255
+ ? "Part of this record, served from this site. Nothing leaves your browser."
256
+ : `Runs on ${host}, and is not part of this record. Nothing is requested from there until you load it.`}
257
+ </p>
258
+ </div>
259
+ )}
260
+ </div>
261
+
262
+ <figcaption className="mt-2 flex items-center justify-between gap-3 text-xs text-fd-muted-foreground">
263
+ <span>{label}</span>
264
+ <a
265
+ href={url}
266
+ target="_blank"
267
+ rel="noreferrer"
268
+ className="inline-flex items-center gap-1 hover:text-fd-foreground"
269
+ >
270
+ {isOwned ? "Open on its own" : `Open on ${host}`}
271
+ <ExternalLink aria-hidden className="size-3" />
272
+ </a>
273
+ </figcaption>
274
+ </figure>
275
+ );
276
+ }
@@ -13,10 +13,11 @@ import { audienceNotice } from "@/lib/audience";
13
13
  * carry the shape of a disclosure.
14
14
  */
15
15
  export function FooterMark(): ReactElement {
16
- if (audienceNotice === null) return <BuiltWith />;
16
+ const notice = audienceNotice();
17
+ if (notice === null) return <BuiltWith />;
17
18
  return (
18
19
  <>
19
- <BuiltWith /> &middot; <span className="text-fd-muted-foreground">{audienceNotice}</span>
20
+ <BuiltWith /> &middot; <span className="text-fd-muted-foreground">{notice}</span>
20
21
  </>
21
22
  );
22
23
  }