@panaversity/ksor 0.0.29 → 0.0.30
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 +41 -0
- package/dist/cli.mjs +7 -3
- package/dist/{gateway-api-8lNruq9e-CuohjtoK.mjs → gateway-api-CbFkHZiU-HvlJRjRB.mjs} +1 -1
- package/dist/gateway.mjs +1 -1
- package/package.json +1 -1
- package/templates/scaffold/.agents/skills/format-checker/check.mjs +3 -1
- package/templates/scaffold/.claude/skills/format-checker/check.mjs +3 -1
- package/templates/scaffold/AGENTS.md +51 -5
- package/templates/scaffold/knowledge/what-is-a-ksor.quiz.yaml +90 -0
- package/templates/scaffold/system/site/app/docs/[[...slug]]/page.tsx +9 -2
- package/templates/scaffold/system/site/app/global.css +68 -0
- package/templates/scaffold/system/site/components/quiz.tsx +321 -0
- package/templates/scaffold/system/site/components/study-aids.tsx +1 -1
- package/templates/scaffold/system/site/lib/attachment-rule.ts +7 -0
- package/templates/scaffold/system/site/lib/attachments.ts +41 -3
- package/templates/scaffold/system/site/lib/deck.ts +3 -12
- package/templates/scaffold/system/site/lib/identity.ts +55 -0
- package/templates/scaffold/system/site/lib/quiz-audit.ts +306 -0
- package/templates/scaffold/system/site/lib/quiz-round.ts +57 -0
- package/templates/scaffold/system/site/lib/quiz.ts +84 -0
- package/templates/scaffold/system/site/source.config.ts +16 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# @panaversity/ksor
|
|
2
2
|
|
|
3
|
+
## 0.0.30
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- fbf149b: Quizzes, as governed attachments of a document.
|
|
8
|
+
|
|
9
|
+
A document in `knowledge/` may now carry `<doc>.quiz.yaml` beside its summary
|
|
10
|
+
and its flashcard deck. It renders at the end of the document's page, under the
|
|
11
|
+
deck: choose an option, see immediately whether you were right, and read the
|
|
12
|
+
explanation before moving on. There is no pass mark — a quiz here checks
|
|
13
|
+
understanding of the record, it does not certify anybody — and answers stay in
|
|
14
|
+
the reader's own browser.
|
|
15
|
+
|
|
16
|
+
A quiz is **part of its document, not a document**: no URL, no sidebar row, no
|
|
17
|
+
`llms.txt` line, no markdown twin, no search entry, and no stable id. That last
|
|
18
|
+
one settles a question worth being explicit about: because `ksor ingest` creates
|
|
19
|
+
no node for a quiz, **the answer key cannot reach the MCP surface at all**.
|
|
20
|
+
There is nothing for an agent to search and nothing for it to read — not by a
|
|
21
|
+
filter that could be forgotten, but because the row does not exist. Governance
|
|
22
|
+
inherits from the parent exactly as the summary and the deck already do.
|
|
23
|
+
|
|
24
|
+
**`pnpm check` and `pnpm build` refuse a quiz a reader could pass without
|
|
25
|
+
reading it**, naming the questions to fix:
|
|
26
|
+
|
|
27
|
+
- `ksor-quiz-answer-bias` — more than 60% of answers at one option position
|
|
28
|
+
- `ksor-quiz-length-bias` — picking the longest or shortest option usually wins
|
|
29
|
+
- `ksor-quiz-answer-run` — four or more questions in a row share an answer
|
|
30
|
+
- `ksor-quiz-contradiction` — an explanation calls the marked answer wrong
|
|
31
|
+
- `ksor-quiz-duplicate-stem` — two questions open with the same 60 characters
|
|
32
|
+
|
|
33
|
+
These are carried from the predecessor, where the same mistakes shipped and were
|
|
34
|
+
found by readers rather than by the project — one quiz put every correct answer
|
|
35
|
+
in the same position across 451 questions. There they lived in a script that was
|
|
36
|
+
run once; here they are part of loading the file, so a quiz that fails them
|
|
37
|
+
cannot be published. The ratio rules do not apply below five questions, where
|
|
38
|
+
enforcing a spread would mean choosing an author's answers for them.
|
|
39
|
+
|
|
40
|
+
`ksor init` ships a quiz on the seed document, so a first `pnpm dev` shows the
|
|
41
|
+
shape. Its own first draft was refused for putting four of five answers at
|
|
42
|
+
option B — the check catching exactly what it was carried for.
|
|
43
|
+
|
|
3
44
|
## 0.0.29
|
|
4
45
|
|
|
5
46
|
### Patch Changes
|
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { n as resolveCommand, r as verbs, t as exitCodes } from "./src-pl4aOpVs.mjs";
|
|
3
|
-
import { A as runProbe$1, C as keyRingFromEnv, D as prewarmPool, E as pooledEndpointFor, F as withPgRetry$1, I as withProbeDeadline$1, L as z$1, M as storedTextSearchConfig, N as tlsPosture, O as readHandler, P as visibleTiers$1, S as embedQueryVlit, T as parseInstanceText$1, _ as buildShippedProvider$1, a as MAX_OUTLINE_LIMIT, b as contentPool$1, c as MissingProviderKeyError$1, d as READ_OUTPUT, f as SEARCH_OUTPUT, g as assertSchemaCompatible, h as assertGovernanceServable$1, i as GovernanceGateError$1, j as searchHandler, k as recordIsUndescribed, l as OUTLINE_OUTPUT, m as TextSearchConfigMismatch, n as EmbeddingSpaceMismatch$1, p as SchemaVersionError, r as FLOOR, s as McpServer$1, t as ContentStoreError$1, u as READ_ONLY, v as checkEmbeddingSpace$1, w as outlineHandler, x as contentPoolMin, y as composeInstructions } from "./gateway-api-
|
|
3
|
+
import { A as runProbe$1, C as keyRingFromEnv, D as prewarmPool, E as pooledEndpointFor, F as withPgRetry$1, I as withProbeDeadline$1, L as z$1, M as storedTextSearchConfig, N as tlsPosture, O as readHandler, P as visibleTiers$1, S as embedQueryVlit, T as parseInstanceText$1, _ as buildShippedProvider$1, a as MAX_OUTLINE_LIMIT, b as contentPool$1, c as MissingProviderKeyError$1, d as READ_OUTPUT, f as SEARCH_OUTPUT, g as assertSchemaCompatible, h as assertGovernanceServable$1, i as GovernanceGateError$1, j as searchHandler, k as recordIsUndescribed, l as OUTLINE_OUTPUT, m as TextSearchConfigMismatch, n as EmbeddingSpaceMismatch$1, p as SchemaVersionError, r as FLOOR, s as McpServer$1, t as ContentStoreError$1, u as READ_ONLY, v as checkEmbeddingSpace$1, w as outlineHandler, x as contentPoolMin, y as composeInstructions } from "./gateway-api-CbFkHZiU-HvlJRjRB.mjs";
|
|
4
4
|
import { chmodSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
6
|
import { InMemoryTransport, LATEST_PROTOCOL_VERSION, createMcpHandler } from "@modelcontextprotocol/server";
|
|
@@ -16,7 +16,7 @@ import { bodyLimit } from "hono/body-limit";
|
|
|
16
16
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
17
17
|
import { parseArgs } from "node:util";
|
|
18
18
|
import { readFile, readdir, stat } from "node:fs/promises";
|
|
19
|
-
//#region ../content-gateway/dist/main-
|
|
19
|
+
//#region ../content-gateway/dist/main-CS5a_8OW.mjs
|
|
20
20
|
/**
|
|
21
21
|
* The default registration — and the ORIGINAL of the file `ksor init` emits.
|
|
22
22
|
*
|
|
@@ -1583,7 +1583,7 @@ async function withPgRetry(op, options = {}) {
|
|
|
1583
1583
|
throw lastError;
|
|
1584
1584
|
}
|
|
1585
1585
|
//#endregion
|
|
1586
|
-
//#region ../content/dist/commands-
|
|
1586
|
+
//#region ../content/dist/commands-CLUS9CX8.mjs
|
|
1587
1587
|
/**
|
|
1588
1588
|
* EVAL-LOCKED constants, quarried verbatim from the oracle
|
|
1589
1589
|
* (sor-agentfactory @ b554f91, config.py) — changing any of these is a
|
|
@@ -4208,6 +4208,10 @@ const ATTACHMENT_SUFFIXES = [
|
|
|
4208
4208
|
{
|
|
4209
4209
|
suffix: ".flashcards.yaml",
|
|
4210
4210
|
kind: "deck"
|
|
4211
|
+
},
|
|
4212
|
+
{
|
|
4213
|
+
suffix: ".quiz.yaml",
|
|
4214
|
+
kind: "quiz"
|
|
4211
4215
|
}
|
|
4212
4216
|
];
|
|
4213
4217
|
/**
|
|
@@ -5,7 +5,7 @@ import { z, z as z$1 } from "zod";
|
|
|
5
5
|
import pg from "pg";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
8
|
-
//#region ../content-gateway/dist/gateway-api-
|
|
8
|
+
//#region ../content-gateway/dist/gateway-api-CbFkHZiU.mjs
|
|
9
9
|
/**
|
|
10
10
|
* The record's system prompt: instance.md's body, preserved beneath a framework
|
|
11
11
|
* floor. Lifted out of server.ts so the public gateway surface can re-export
|
package/dist/gateway.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { L as z$1, O as readHandler, a as MAX_OUTLINE_LIMIT, d as READ_OUTPUT, f as SEARCH_OUTPUT, j as searchHandler, k as recordIsUndescribed, l as OUTLINE_OUTPUT, o as MAX_SEARCH_K, r as FLOOR, s as McpServer$1, u as READ_ONLY, w as outlineHandler, y as composeInstructions } from "./gateway-api-
|
|
1
|
+
import { L as z$1, O as readHandler, a as MAX_OUTLINE_LIMIT, d as READ_OUTPUT, f as SEARCH_OUTPUT, j as searchHandler, k as recordIsUndescribed, l as OUTLINE_OUTPUT, o as MAX_SEARCH_K, r as FLOOR, s as McpServer$1, u as READ_ONLY, w as outlineHandler, y as composeInstructions } from "./gateway-api-CbFkHZiU-HvlJRjRB.mjs";
|
|
2
2
|
export { FLOOR, MAX_OUTLINE_LIMIT, MAX_SEARCH_K, McpServer$1 as McpServer, OUTLINE_OUTPUT, READ_ONLY, READ_OUTPUT, SEARCH_OUTPUT, composeInstructions, outlineHandler, readHandler, recordIsUndescribed, searchHandler, z$1 as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@panaversity/ksor",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.30",
|
|
4
4
|
"description": "Knowledge System of Record — compile governed markdown into a static site for people and an MCP server for AI agents, with citations and measured abstention.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"abstention",
|
|
@@ -41,7 +41,7 @@ const ASSET_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".gif", ".svg", ".web
|
|
|
41
41
|
// rules below. This mirrors packages/content/src/lib/attachment-rule.ts, which
|
|
42
42
|
// this dependency-free file cannot import; ATTACHMENT_CASES there is the table
|
|
43
43
|
// both are held to.
|
|
44
|
-
const ATTACHMENT_SUFFIXES = [".summary.md", ".summary.mdx", ".flashcards.yaml"];
|
|
44
|
+
const ATTACHMENT_SUFFIXES = [".summary.md", ".summary.mdx", ".flashcards.yaml", ".quiz.yaml"];
|
|
45
45
|
// One character off a real attachment, refused BY NAME: `.yml` reaches the
|
|
46
46
|
// site bundler's `Unknown file type` throw, which names the path and nothing
|
|
47
47
|
// about the rule.
|
|
@@ -49,6 +49,8 @@ const ATTACHMENT_NEAR_MISSES = [
|
|
|
49
49
|
[".flashcards.yml", ".flashcards.yaml"],
|
|
50
50
|
[".flashcards.json", ".flashcards.yaml"],
|
|
51
51
|
[".summary.markdown", ".summary.md"],
|
|
52
|
+
[".quiz.yml", ".quiz.yaml"],
|
|
53
|
+
[".quiz.json", ".quiz.yaml"],
|
|
52
54
|
];
|
|
53
55
|
|
|
54
56
|
/** The attachment suffix this name carries, or null. A dotfile has no stem. */
|
|
@@ -41,7 +41,7 @@ const ASSET_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".gif", ".svg", ".web
|
|
|
41
41
|
// rules below. This mirrors packages/content/src/lib/attachment-rule.ts, which
|
|
42
42
|
// this dependency-free file cannot import; ATTACHMENT_CASES there is the table
|
|
43
43
|
// both are held to.
|
|
44
|
-
const ATTACHMENT_SUFFIXES = [".summary.md", ".summary.mdx", ".flashcards.yaml"];
|
|
44
|
+
const ATTACHMENT_SUFFIXES = [".summary.md", ".summary.mdx", ".flashcards.yaml", ".quiz.yaml"];
|
|
45
45
|
// One character off a real attachment, refused BY NAME: `.yml` reaches the
|
|
46
46
|
// site bundler's `Unknown file type` throw, which names the path and nothing
|
|
47
47
|
// about the rule.
|
|
@@ -49,6 +49,8 @@ const ATTACHMENT_NEAR_MISSES = [
|
|
|
49
49
|
[".flashcards.yml", ".flashcards.yaml"],
|
|
50
50
|
[".flashcards.json", ".flashcards.yaml"],
|
|
51
51
|
[".summary.markdown", ".summary.md"],
|
|
52
|
+
[".quiz.yml", ".quiz.yaml"],
|
|
53
|
+
[".quiz.json", ".quiz.yaml"],
|
|
52
54
|
];
|
|
53
55
|
|
|
54
56
|
/** The attachment suffix this name carries, or null. A dotfile has no stem. */
|
|
@@ -506,11 +506,12 @@ CI — and a first deploy without it serves an empty record. Full walkthrough:
|
|
|
506
506
|
refused.
|
|
507
507
|
- Images and assets live in `knowledge/` beside the document that uses them,
|
|
508
508
|
referenced by relative links. A relative link must never leave `knowledge/`.
|
|
509
|
-
- **Study attachments.** A document may carry
|
|
510
|
-
after it, in the same folder: `<doc>.summary.md` (a short précis)
|
|
511
|
-
`<doc>.flashcards.yaml` (a recall deck)
|
|
512
|
-
|
|
513
|
-
document's
|
|
509
|
+
- **Study attachments.** A document may carry three optional companions named
|
|
510
|
+
after it, in the same folder: `<doc>.summary.md` (a short précis),
|
|
511
|
+
`<doc>.flashcards.yaml` (a recall deck) and `<doc>.quiz.yaml` (a
|
|
512
|
+
multiple-choice check). The summary appears as a second tab beside the
|
|
513
|
+
document's own words; the deck and the quiz appear at the END of that
|
|
514
|
+
document's page. None of them appears anywhere else in the site.
|
|
514
515
|
|
|
515
516
|
An attachment is **part of its document**, not a document. It has no URL of
|
|
516
517
|
its own, no sidebar row, no line in `llms.txt`, and no identity an agent can
|
|
@@ -549,6 +550,51 @@ CI — and a first deploy without it serves an empty record. Full walkthrough:
|
|
|
549
550
|
kept in the reader's own browser, so it is per-person and per-device, and it
|
|
550
551
|
is not part of the record.
|
|
551
552
|
|
|
553
|
+
A **quiz** is the same idea with an answer to choose. Each question carries
|
|
554
|
+
two to six options, `answer` as the zero-based index of the correct one, a
|
|
555
|
+
required `explanation`, and an optional `source` naming where in the document
|
|
556
|
+
the answer lives:
|
|
557
|
+
|
|
558
|
+
```yaml
|
|
559
|
+
quiz:
|
|
560
|
+
title: Expense approvals
|
|
561
|
+
questionsPerRound: 10 # optional; the default
|
|
562
|
+
questions:
|
|
563
|
+
- question: Who approves a purchase above the threshold?
|
|
564
|
+
options:
|
|
565
|
+
- A second approver, independent of the requester
|
|
566
|
+
- The requester's own manager, in every case
|
|
567
|
+
- The finance team, after the purchase completes
|
|
568
|
+
answer: 0
|
|
569
|
+
explanation: Why that is right, and why the others are not.
|
|
570
|
+
source: Approvals — thresholds
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
The reader sees whether they were right **immediately**, then the
|
|
574
|
+
explanation — a quiz here teaches through the mistake, and it does not
|
|
575
|
+
certify anybody, so there is no pass mark. Answers stay in the reader's
|
|
576
|
+
browser and are sent nowhere. A bank larger than `questionsPerRound` is drawn
|
|
577
|
+
from at random and offers another round; a smaller one is simply asked in
|
|
578
|
+
full.
|
|
579
|
+
|
|
580
|
+
**`pnpm check` and `pnpm build` both refuse a quiz a reader could pass
|
|
581
|
+
without reading**, and this is worth knowing before you write one, because
|
|
582
|
+
it is easy to trip by accident:
|
|
583
|
+
|
|
584
|
+
| Refusal | Means |
|
|
585
|
+
| -------------------------- | ----------------------------------------------------- |
|
|
586
|
+
| `ksor-quiz-answer-bias` | more than 60% of answers sit at one option position |
|
|
587
|
+
| `ksor-quiz-length-bias` | picking the longest (or shortest) option usually wins |
|
|
588
|
+
| `ksor-quiz-answer-run` | four or more questions in a row share an answer |
|
|
589
|
+
| `ksor-quiz-contradiction` | an explanation calls the marked answer wrong |
|
|
590
|
+
| `ksor-quiz-duplicate-stem` | two questions open with the same 60 characters |
|
|
591
|
+
|
|
592
|
+
So: move the correct answer around, and keep the options close in length.
|
|
593
|
+
The ratio rules do not apply below five questions, where spreading answers
|
|
594
|
+
would mean the checker choosing them for you. These checks come from the
|
|
595
|
+
predecessor, where the same mistakes shipped and were found by readers — one
|
|
596
|
+
quiz had every correct answer in the same position across 451 questions.
|
|
597
|
+
|
|
552
598
|
- Copy load-bearing values (numbers, thresholds, dates) exactly from their
|
|
553
599
|
source, and name the source in `provenance`.
|
|
554
600
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# A quiz for what-is-a-ksor.md.
|
|
2
|
+
#
|
|
3
|
+
# Every question and every answer states only what its parent document states —
|
|
4
|
+
# a quiz is a way of checking the record, never a second source. Ask your coding
|
|
5
|
+
# agent to write one from a document, and to check each answer back against it.
|
|
6
|
+
#
|
|
7
|
+
# Two habits worth copying. The options are kept close in LENGTH, because if the
|
|
8
|
+
# correct answer is reliably the longest one a reader passes by looking rather
|
|
9
|
+
# than by reading. And the correct answer moves POSITION between questions: a
|
|
10
|
+
# quiz whose answer is usually B is a quiz you can pass without the document.
|
|
11
|
+
# `pnpm check` and `pnpm build` both refuse a quiz where either is true — the
|
|
12
|
+
# first draft of this one was refused for putting four of five answers at B.
|
|
13
|
+
quiz:
|
|
14
|
+
title: Check yourself
|
|
15
|
+
description: Five questions on what this record is, and what it is not.
|
|
16
|
+
questions:
|
|
17
|
+
- question: A spreadsheet and the ledger disagree about a number. Which one is authoritative?
|
|
18
|
+
options:
|
|
19
|
+
- Whichever of the two was most recently edited
|
|
20
|
+
- The spreadsheet, being closer to the daily work
|
|
21
|
+
- The ledger, because it is the governing copy
|
|
22
|
+
- Neither, until a person reconciles the two
|
|
23
|
+
answer: 2
|
|
24
|
+
explanation: >
|
|
25
|
+
A system of record is the copy that governs, so the ledger wins by
|
|
26
|
+
definition rather than by being newer or closer to the work. Recency is
|
|
27
|
+
not authority: an edit made this morning in a spreadsheet nobody governs
|
|
28
|
+
is still ungoverned. Reconciliation is something a record makes possible,
|
|
29
|
+
not a precondition for having an answer at all.
|
|
30
|
+
source: The opening definition
|
|
31
|
+
|
|
32
|
+
- question: What does a KSoR settle that a traditional system of record does not?
|
|
33
|
+
options:
|
|
34
|
+
- The state of a business, such as its balances
|
|
35
|
+
- Which vendor an organization has chosen to use
|
|
36
|
+
- How much storage the organization is paying for
|
|
37
|
+
- What the organization knows and how it operates
|
|
38
|
+
answer: 3
|
|
39
|
+
explanation: >
|
|
40
|
+
A traditional system of record settles the state of a business — what is
|
|
41
|
+
owed, what is owned, what was transacted. A KSoR settles the layer above
|
|
42
|
+
it: which policies apply, which thresholds are approved, and what a term
|
|
43
|
+
means inside this organization. Vendor choice and storage cost are
|
|
44
|
+
consequences of running one, never what it is authoritative for.
|
|
45
|
+
source: What this record settles
|
|
46
|
+
|
|
47
|
+
- question: Why can an ordinary assistant not tell you which of its sentences were checked?
|
|
48
|
+
options:
|
|
49
|
+
- It answers from everything it has ever read
|
|
50
|
+
- It was not given enough context to work with
|
|
51
|
+
- Its answers are checked, but only in summary
|
|
52
|
+
- It lacks permission to reveal its own sources
|
|
53
|
+
answer: 0
|
|
54
|
+
explanation: >
|
|
55
|
+
It answers from everything it has ever read, and nothing in that process
|
|
56
|
+
separates a checked claim from an unchecked one. This is not a context
|
|
57
|
+
problem and not a permissions problem: more context cannot create a
|
|
58
|
+
distinction that was never recorded in the first place. The record closes
|
|
59
|
+
the gap by governing what gets read.
|
|
60
|
+
source: Why an assistant cannot answer that question
|
|
61
|
+
|
|
62
|
+
- question: What problem does a KSoR exist to solve?
|
|
63
|
+
options:
|
|
64
|
+
- Storage costs across a growing organization
|
|
65
|
+
- Scatter, with no authoritative copy to trust
|
|
66
|
+
- The speed at which an assistant can reply
|
|
67
|
+
- The difficulty of writing documentation well
|
|
68
|
+
answer: 1
|
|
69
|
+
explanation: >
|
|
70
|
+
The problem is scatter: knowledge spread across wikis, decks, PDFs,
|
|
71
|
+
prompts and somebody's memory, with no authoritative answer to which of
|
|
72
|
+
them an agent should trust. Speed and cost are not what the record
|
|
73
|
+
addresses, and writing well is valuable but does not by itself tell an
|
|
74
|
+
agent which of two documents governs.
|
|
75
|
+
source: The problem it solves
|
|
76
|
+
|
|
77
|
+
- question: Abstention — answering "not in this corpus" — is best described as what?
|
|
78
|
+
options:
|
|
79
|
+
- An error state the record should minimise
|
|
80
|
+
- A temporary gap, pending the next ingest
|
|
81
|
+
- A setting an operator turns on when ready
|
|
82
|
+
- A correct answer the record is meant to give
|
|
83
|
+
answer: 3
|
|
84
|
+
explanation: >
|
|
85
|
+
Abstention is a correct answer, never an error and never a licence to
|
|
86
|
+
fall back on general model knowledge. A record that answers everything
|
|
87
|
+
has stopped being a record of anything in particular. It is not a gap
|
|
88
|
+
waiting to be filled, and while the gate that enforces it is configured
|
|
89
|
+
deliberately, the honesty it expresses is the product itself.
|
|
90
|
+
source: What this record settles, and what it does not
|
|
@@ -17,8 +17,9 @@ import { showGovernance } from "@/lib/shared";
|
|
|
17
17
|
import { RecordToc, TocItems } from "@/components/record-toc";
|
|
18
18
|
import { RecordViews } from "@/components/record-views";
|
|
19
19
|
import { Flashcards } from "@/components/flashcards";
|
|
20
|
+
import { Quiz } from "@/components/quiz";
|
|
20
21
|
import { StudyAids } from "@/components/study-aids";
|
|
21
|
-
import { deckFor, summaryFor } from "@/lib/attachments";
|
|
22
|
+
import { deckFor, quizFor, summaryFor } from "@/lib/attachments";
|
|
22
23
|
import { readingMinutes } from "@/lib/reading-time";
|
|
23
24
|
|
|
24
25
|
export default async function Page(props: PageProps<"/docs/[[...slug]]">) {
|
|
@@ -32,6 +33,7 @@ export default async function Page(props: PageProps<"/docs/[[...slug]]">) {
|
|
|
32
33
|
const summary = summaryFor(page.path);
|
|
33
34
|
const Summary = summary?.body ?? null;
|
|
34
35
|
const deck = deckFor(page.path);
|
|
36
|
+
const quiz = quizFor(page.path);
|
|
35
37
|
// Counted at BUILD time from the document's own markdown, so the figure is in
|
|
36
38
|
// the shipped HTML for a reader with a failed bundle, a crawler and an agent
|
|
37
39
|
// alike. The predecessor measured the rendered DOM after paint, which put it
|
|
@@ -157,7 +159,12 @@ export default async function Page(props: PageProps<"/docs/[[...slug]]">) {
|
|
|
157
159
|
region, so the quiz that will sit beside the deck is a child here and
|
|
158
160
|
not a new argument about where it goes. Renders nothing at all when
|
|
159
161
|
the document has no study aids. */}
|
|
160
|
-
|
|
162
|
+
{/* Recall first, then the check on it — and each renders only if the
|
|
163
|
+
document carries one, so a page with just a quiz shows just a quiz. */}
|
|
164
|
+
<StudyAids>
|
|
165
|
+
{deck === null ? null : <Flashcards deck={deck} />}
|
|
166
|
+
{quiz === null ? null : <Quiz quiz={quiz} />}
|
|
167
|
+
</StudyAids>
|
|
161
168
|
{/* A folder's index page lists what the folder holds. Without it the
|
|
162
169
|
page ended at its own sentence and the documents below it were
|
|
163
170
|
reachable only from the sidebar (research/site-design.md F5). Empty
|
|
@@ -274,6 +274,74 @@ html > body[data-scroll-locked] {
|
|
|
274
274
|
--ksor-caution: #e0906e;
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
+
/* Right and wrong, for the quiz — the ONE place this record spends green and
|
|
278
|
+
red, and it earns them: a reader checking an answer is asking a true/false
|
|
279
|
+
question, and the accent cannot say "this is correct" while also saying
|
|
280
|
+
"this is what you picked". Both are needed at once when you answer wrongly.
|
|
281
|
+
|
|
282
|
+
Colour is never the only channel: the correct option also carries a check,
|
|
283
|
+
the wrong one a cross, and the line above states the verdict in words — so
|
|
284
|
+
the pair works for a reader who cannot distinguish them. Declared as
|
|
285
|
+
light/dark pairs on the root for the reason `--ksor-caution` above records.
|
|
286
|
+
|
|
287
|
+
Deliberately NOT `--ksor-caution`, which means "the record withdrew this".
|
|
288
|
+
A wrong answer is not a governance state. */
|
|
289
|
+
:root {
|
|
290
|
+
--ksor-correct: #1a7f4b;
|
|
291
|
+
--ksor-incorrect: #b3261e;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.dark {
|
|
295
|
+
--ksor-correct: #6edaa0;
|
|
296
|
+
--ksor-incorrect: #f2897f;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/* The three states an answered question shows at once. Written as CLASSES,
|
|
300
|
+
the way `.ksor-caution` below is, rather than as Tailwind arbitrary values:
|
|
301
|
+
`border-[color:var(--x)]` emits a rule whose `var()` did not resolve on the
|
|
302
|
+
element in a real build (found live 2026-08-23 — the class was present, the
|
|
303
|
+
rule was in the stylesheet, the token computed correctly on the element, and
|
|
304
|
+
the border still painted the default hairline). A plain class has none of
|
|
305
|
+
that ambiguity, and this file is already where this record's semantic colour
|
|
306
|
+
lives. */
|
|
307
|
+
.ksor-answer-correct {
|
|
308
|
+
border-color: var(--ksor-correct);
|
|
309
|
+
background-color: color-mix(in oklab, var(--ksor-correct) 10%, transparent);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.ksor-answer-wrong {
|
|
313
|
+
border-color: var(--ksor-incorrect);
|
|
314
|
+
background-color: color-mix(in oklab, var(--ksor-incorrect) 10%, transparent);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/* "This is the one you picked" — kept in the accent, and kept SEPARATE from
|
|
318
|
+
right/wrong, because after a wrong answer a reader needs all three facts. */
|
|
319
|
+
.ksor-answer-mine {
|
|
320
|
+
outline: 2px solid var(--color-fd-primary);
|
|
321
|
+
outline-offset: 1px;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.ksor-answer-correct-text {
|
|
325
|
+
color: var(--ksor-correct);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.ksor-answer-wrong-text {
|
|
329
|
+
color: var(--ksor-incorrect);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.dark .ksor-answer-correct,
|
|
333
|
+
.dark .ksor-answer-wrong {
|
|
334
|
+
background-color: color-mix(in oklab, currentColor 0%, transparent);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.dark .ksor-answer-correct {
|
|
338
|
+
background-color: color-mix(in oklab, var(--ksor-correct) 14%, transparent);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.dark .ksor-answer-wrong {
|
|
342
|
+
background-color: color-mix(in oklab, var(--ksor-incorrect) 14%, transparent);
|
|
343
|
+
}
|
|
344
|
+
|
|
277
345
|
.ksor-caution {
|
|
278
346
|
border-color: color-mix(in oklab, var(--ksor-caution) 32%, transparent);
|
|
279
347
|
border-left-color: var(--ksor-caution);
|