org-knowledge-layer 0.4.1__tar.gz → 0.5.0__tar.gz

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 (145) hide show
  1. {org_knowledge_layer-0.4.1/src/okl/scaffold → org_knowledge_layer-0.5.0/.claude}/hooks/userpromptsubmit-okl-check.sh +4 -2
  2. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/.gitignore +2 -0
  3. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/AGENTS.md +8 -1
  4. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/CHANGELOG.md +36 -0
  5. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/CLAUDE.md +8 -1
  6. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/PKG-INFO +16 -2
  7. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/README.md +14 -0
  8. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/docs/decisions/2026-07-21-subject-tags-controlled-vocabulary.md +41 -0
  9. org_knowledge_layer-0.5.0/docs/okl-retrieval-pipeline.svg +89 -0
  10. org_knowledge_layer-0.5.0/docs/posts/04-the-elegant-change-was-the-wrong-one.md +55 -0
  11. org_knowledge_layer-0.5.0/docs/render_pipeline_diagram.py +287 -0
  12. org_knowledge_layer-0.5.0/evals/REPORT.md +838 -0
  13. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/evals/ab_harness.py +134 -6
  14. org_knowledge_layer-0.5.0/evals/preflight.py +94 -0
  15. org_knowledge_layer-0.5.0/evals/results/ab-20260902-0538.json +439 -0
  16. org_knowledge_layer-0.5.0/evals/results/ab-20260902-1216.json +27 -0
  17. org_knowledge_layer-0.5.0/evals/results/ab-20260903-0157.json +441 -0
  18. org_knowledge_layer-0.5.0/evals/results/ab-20260903-0308.json +441 -0
  19. org_knowledge_layer-0.5.0/evals/results/ab-20260903-1214.json +441 -0
  20. org_knowledge_layer-0.5.0/evals/results/ab-20260903-1323.json +443 -0
  21. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/evals/tasks.jsonl +1 -1
  22. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/hooks/stop-okl-encode.sh +11 -3
  23. {org_knowledge_layer-0.4.1/.claude → org_knowledge_layer-0.5.0}/hooks/userpromptsubmit-okl-check.sh +4 -2
  24. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/pyproject.toml +2 -2
  25. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/seed/dotnet-canon.json +8 -0
  26. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/seed/dotnet-decisions.json +11 -0
  27. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/seed/dotnet-defects.json +4 -2
  28. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/seed/frontend-canon.json +2 -0
  29. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/seed/geospatial-deeptime-defects.json +2 -1
  30. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/seed/geospatial-defects.json +8 -4
  31. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/seed/geospatial-enforcement-defects.json +2 -1
  32. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/seed/react-defects.json +4 -2
  33. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/cli.py +53 -4
  34. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/client.py +28 -8
  35. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/core.py +35 -16
  36. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/claude/commands/seed-from-codebase.md +1 -1
  37. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/claude/commands/seed-from-docs.md +1 -1
  38. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/hooks/stop-okl-encode.sh +11 -3
  39. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0/src/okl/scaffold}/hooks/userpromptsubmit-okl-check.sh +4 -2
  40. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/store.py +66 -10
  41. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/tests/test_okl.py +284 -33
  42. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/tests/test_scaffold.py +116 -0
  43. org_knowledge_layer-0.4.1/evals/REPORT.md +0 -369
  44. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/.claude/agents/architecture-reviewer.md +0 -0
  45. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/.claude/hooks/stop-okl-encode.sh +0 -0
  46. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/.claude/settings.json +0 -0
  47. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/.coverage +0 -0
  48. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  49. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  50. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/.github/dependabot.yml +0 -0
  51. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/.github/pull_request_template.md +0 -0
  52. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/.github/workflows/ci.yml +0 -0
  53. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/.github/workflows/okl-verify.yml +0 -0
  54. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/.github/workflows/publish.yml +0 -0
  55. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/CONTRIBUTING.md +0 -0
  56. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/LICENSE +0 -0
  57. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/SECURITY.md +0 -0
  58. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/ci/check-diagram-figures.sh +0 -0
  59. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/ci/okl-verify.yml +0 -0
  60. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/ci/review-agent.sh +0 -0
  61. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/docs/DEPLOY.md +0 -0
  62. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/docs/ab-results-chart.png +0 -0
  63. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/docs/ab-results-chart.svg +0 -0
  64. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/docs/decisions/2026-07-17-flat-retrieval-until-scale.md +0 -0
  65. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/docs/okl-how-it-works.excalidraw +0 -0
  66. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/docs/okl-how-it-works.svg +0 -0
  67. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/docs/okl-sixth-surface.excalidraw +0 -0
  68. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/docs/okl-sixth-surface.svg +0 -0
  69. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/docs/posts/01-memory-that-outlives-the-run.md +0 -0
  70. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/docs/posts/02-dont-let-a-step-grade-itself.md +0 -0
  71. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/docs/posts/03-enforcement-or-good-intentions.md +0 -0
  72. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/evals/README.md +0 -0
  73. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/evals/results/ab-20260829-2300.json +0 -0
  74. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/evals/results/ab-20260829-2315.json +0 -0
  75. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/evals/results/ab-20260830-0003.json +0 -0
  76. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/evals/results/ab-20260830-0148.json +0 -0
  77. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/evals/results/ab-20260901-0133.json +0 -0
  78. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/evals/results/ab-20260901-1238.json +0 -0
  79. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/evals/results/e2e-20260830/README.md +0 -0
  80. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/evals/results/e2e-20260830/briefed-userpromptsubmit-lint.yml +0 -0
  81. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/evals/results/e2e-20260830/control-lint.yml +0 -0
  82. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/evals/results/e2e-20260830/hook.log +0 -0
  83. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/evals/results/e2e-20260830/service-record-500.log +0 -0
  84. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/evals/results/e2e-20260830/session-briefed-pretooluse.txt +0 -0
  85. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/evals/results/e2e-20260830/session-briefed-userpromptsubmit.txt +0 -0
  86. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/evals/results/e2e-20260830/session-control.txt +0 -0
  87. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/gates/check-canon-size.sh +0 -0
  88. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/gates/check-diagram-pairs.sh +0 -0
  89. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/gates/check-doc-orphans.sh +0 -0
  90. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/gates/check-links.sh +0 -0
  91. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/gates/check-retractions.sh +0 -0
  92. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/gates/check-tombstones.sh +0 -0
  93. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/gates/run-gates.sh +0 -0
  94. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/seed/dotnet-review-surfaces.json +0 -0
  95. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/seed/geospatial-eval-defects.json +0 -0
  96. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/seed/rag-defects.json +0 -0
  97. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/__init__.py +0 -0
  98. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/__main__.py +0 -0
  99. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/bootstrap.py +0 -0
  100. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/drift.py +0 -0
  101. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/mcp_server.py +0 -0
  102. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/MANIFEST.md +0 -0
  103. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/ci/dependabot.yml +0 -0
  104. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/ci/method-gates.yml +0 -0
  105. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/ci/okl-verify.yml +0 -0
  106. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/ci/review-agent.sh +0 -0
  107. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/claude/agents/architecture-reviewer.md +0 -0
  108. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/claude/commands/check-rules.md +0 -0
  109. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/claude/commands/feature-spec.md +0 -0
  110. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/claude/rules/example-area.md +0 -0
  111. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/claude/skills/RECOMMENDED-COMPANIONS.md +0 -0
  112. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/claude/skills/encoding-loop/SKILL.md +0 -0
  113. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/claude/skills/verify-before-claiming/SKILL.md +0 -0
  114. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/evals/README.md +0 -0
  115. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/evals/cases.jsonl +0 -0
  116. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/evals/run_evals.py +0 -0
  117. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/gates/check-canon-size.sh +0 -0
  118. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/gates/check-diagram-pairs.sh +0 -0
  119. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/gates/check-doc-orphans.sh +0 -0
  120. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/gates/check-links.sh +0 -0
  121. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/gates/check-retractions.sh +0 -0
  122. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/gates/check-tombstones.sh +0 -0
  123. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/gates/run-gates.sh +0 -0
  124. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/hooks/hooks.json +0 -0
  125. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/plugin/plugin.json +0 -0
  126. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/dotnet/README.md +0 -0
  127. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/dotnet/rules/architecture.md +0 -0
  128. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/dotnet/rules/messaging.md +0 -0
  129. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/dotnet/rules/performance-and-data.md +0 -0
  130. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/dotnet/rules/security.md +0 -0
  131. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/geospatial/README.md +0 -0
  132. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/geospatial/rules/geospatial-ml.md +0 -0
  133. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/python-rag/README.md +0 -0
  134. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/python-rag/rules/fastapi-backend.md +0 -0
  135. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/python-rag/rules/project-structure.md +0 -0
  136. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/python-rag/rules/rag-pipeline.md +0 -0
  137. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/react/README.md +0 -0
  138. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/react/rules/frontend.md +0 -0
  139. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/registries/RETRACTIONS.md +0 -0
  140. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/registries/tombstones.txt +0 -0
  141. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/root/CLAUDE.md +0 -0
  142. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold/root/METHOD.md +0 -0
  143. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/scaffold_cmd.py +0 -0
  144. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/seed.py +0 -0
  145. {org_knowledge_layer-0.4.1 → org_knowledge_layer-0.5.0}/src/okl/service.py +0 -0
@@ -36,8 +36,10 @@ resolve_okl() {
36
36
  if ! OKL=$(resolve_okl); then
37
37
  [ "${OKL_OFFLINE:-0}" = "1" ] && exit 0
38
38
  echo "okl NOT FOUND — blocking (a check that can't run must not pass as clean)." >&2
39
- echo "Install it (pip install okl), set OKL_BIN, or re-run 'okl init' from a shell where it" >&2
40
- echo "works (pins okl_bin into .okl/config.json). OKL_OFFLINE=1 proceeds without the layer." >&2
39
+ echo "Install it: pip install org-knowledge-layer. NOT 'pip install okl' — PyPI refuses that" >&2
40
+ echo "name as confusable, so it fails and looks like the tool does not exist." >&2
41
+ echo "Or set OKL_BIN, or re-run 'okl init' from a shell where okl works (that pins okl_bin" >&2
42
+ echo "into .okl/config.json). OKL_OFFLINE=1 proceeds without the layer." >&2
41
43
  exit 2
42
44
  fi
43
45
 
@@ -19,3 +19,5 @@ e2e/
19
19
 
20
20
  #Ignore vscode AI rules
21
21
  .github/instructions/codacy.instructions.md
22
+ okl.db-wal
23
+ okl.db-shm
@@ -9,7 +9,14 @@ The hooks fire on your own session: `UserPromptSubmit` injects the store's brief
9
9
  your context before you start; `Stop` blocks your first stop to ask what was learned.
10
10
  Answer it honestly — record with a deliberate scope (`org` spreads to every repo,
11
11
  `repo` stays local) and tags from the closed vocabulary in `store.KNOWN_TAGS`. Growing
12
- that vocabulary is an edit to the set plus a note in the tags ADR, never an ad-hoc tag.
12
+ that vocabulary is a deliberate act, never an ad-hoc tag: `okl record --type Vocabulary
13
+ --scope org --title <tag>` declares one for THIS store (a Go or Rust team needs no fork),
14
+ and editing `KNOWN_TAGS` changes the floor every store ships with.
15
+
16
+ - **`applies_to` is where a lesson is TRUE; tags are where it was FOUND.** Leave it unset
17
+ unless the lesson is false or meaningless off that stack — unset reaches every repo and
18
+ is the safe default, while a wrong value hides the record silently. Never derive it from
19
+ a tag: that is the reverted §4d defect, which measured worse than no filter at all.
13
20
 
14
21
  - **Verification is evidence-based**: `okl verify <id> --run "<check>" --expect "<signal>"`.
15
22
  Never re-record with `--verified` to clear drift — run the check.
@@ -1,5 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### The vocabulary is no longer fixed in the package
6
+
7
+ - **A store can declare its own subject tags.** `KNOWN_TAGS` remains, but as the *floor*
8
+ every store ships with rather than the whole vocabulary. Widen it by recording a
9
+ `Vocabulary` node, whose title is the tag:
10
+
11
+ ```bash
12
+ okl record --type Vocabulary --scope org --title rust --body "Rust services."
13
+ okl record --type Rule --scope org --tags rust --title "Prefer ? over unwrap in handlers"
14
+ ```
15
+
16
+ Before this, a Go, Rust or PowerShell team could not file a lesson under its own language
17
+ without forking the package, which is a hard stop on adoption for a tool whose claim is
18
+ that a lesson learned in one place reaches another. What the closed vocabulary was *for*
19
+ survives: it catches the typo that files a record where nobody looks, and closed-per-store
20
+ still does, so `rustt` is refused in a store that declared `rust`. What changes is who may
21
+ grow it. `STACK_TAGS`, which gates `applies_to`, is deliberately not extended.
22
+
23
+ - **`frontend` and `prose` added to the floor.** Neither is specific to one org: any repo
24
+ that renders markup has frontend lessons, and any repo that governs its writing has prose
25
+ ones. `react` was the nearest existing tag for the first and is wrong, being a stack tag.
26
+
27
+ ### Fixed
28
+
29
+ - **The hooks no longer tell you to run `pip install okl`, which cannot work.** PyPI refuses
30
+ `okl` as confusable, so the distribution is `org-knowledge-layer` and `okl` is only the
31
+ console script. The wrong name was in the `UserPromptSubmit` hook's not-found message,
32
+ which fires *exactly* when someone has no working install and sent them to a 404 that
33
+ reads as "this tool was never published." `okl init` stamps that hook into every repo it
34
+ touches, so the error propagated with adoption. Re-run `okl init` to pick up the new file.
35
+ - `cli.py`'s MCP hint said `pip install okl[mcp]`; now `pip install 'org-knowledge-layer[mcp]'`,
36
+ quoted because zsh globs unquoted brackets.
37
+
38
+
3
39
  ## 0.4.1
4
40
 
5
41
  ### Security — affects the CI workflow already in your repo
@@ -9,7 +9,14 @@ The hooks fire on your own session: `UserPromptSubmit` injects the store's brief
9
9
  your context before you start; `Stop` blocks your first stop to ask what was learned.
10
10
  Answer it honestly — record with a deliberate scope (`org` spreads to every repo,
11
11
  `repo` stays local) and tags from the closed vocabulary in `store.KNOWN_TAGS`. Growing
12
- that vocabulary is an edit to the set plus a note in the tags ADR, never an ad-hoc tag.
12
+ that vocabulary is a deliberate act, never an ad-hoc tag: `okl record --type Vocabulary
13
+ --scope org --title <tag>` declares one for THIS store (a Go or Rust team needs no fork),
14
+ and editing `KNOWN_TAGS` changes the floor every store ships with.
15
+
16
+ - **`applies_to` is where a lesson is TRUE; tags are where it was FOUND.** Leave it unset
17
+ unless the lesson is false or meaningless off that stack — unset reaches every repo and
18
+ is the safe default, while a wrong value hides the record silently. Never derive it from
19
+ a tag: that is the reverted §4d defect, which measured worse than no filter at all.
13
20
 
14
21
  - **Verification is evidence-based**: `okl verify <id> --run "<check>" --expect "<signal>"`.
15
22
  Never re-record with `--verified` to clear drift — run the check.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: org-knowledge-layer
3
- Version: 0.4.1
3
+ Version: 0.5.0
4
4
  Summary: Org Knowledge Layer — an installable sixth surface that carries encoded engineering lessons across repos.
5
5
  Author: Joshua Dell
6
6
  License: MIT
@@ -19,7 +19,7 @@ Requires-Dist: httpx>=0.27; extra == 'dev'
19
19
  Requires-Dist: mypy==2.3.1; extra == 'dev'
20
20
  Requires-Dist: pydantic>=2; extra == 'dev'
21
21
  Requires-Dist: pytest-cov==7.1.0; extra == 'dev'
22
- Requires-Dist: pytest==8.4.2; extra == 'dev'
22
+ Requires-Dist: pytest==9.1.1; extra == 'dev'
23
23
  Requires-Dist: ruff==0.16.5; extra == 'dev'
24
24
  Requires-Dist: uvicorn>=0.29; extra == 'dev'
25
25
  Provides-Extra: mcp
@@ -210,6 +210,20 @@ with receipts, not a benchmark.
210
210
 
211
211
  <img src="docs/okl-how-it-works.svg" alt="One repo records a lesson; every other repo is briefed on it before its next task. The store is typed, scoped and tagged; every verification stamp carries the check that earned it." width="100%">
212
212
 
213
+ ### How a briefing is actually built
214
+
215
+ `okl check` is a retrieval pipeline: seven stages turn a task sentence and the whole corpus
216
+ into the twelve records an agent reads. Two stages can drop a record, and only one of them
217
+ is entitled to — the distinction that this project got wrong once and measured its way out
218
+ of ([REPORT §4d](evals/REPORT.md)).
219
+
220
+ <img src="docs/okl-retrieval-pipeline.svg" alt="Seven stages: the corpus, a BM25 fetch of three times the limit, a scope gate, the exclusive applies_to filter, the inclusive tags-times-interests filter, a top-12 cutoff, bucketing by type, and rendering. Each stage shows how many records survive it." width="100%">
221
+
222
+ Every count in that diagram is **traced**, not transcribed — `docs/render_pipeline_diagram.py`
223
+ runs the real `store.search` and `core._in_scope` against a store seeded from `seed/`, and
224
+ `test_pipeline_diagram_is_current` fails if the committed render is not what the generator
225
+ produces today. Change a BM25 weight or a filter predicate and the diagram goes red with it.
226
+
213
227
  ### The mental model
214
228
 
215
229
  `okl` stores small, typed **notes** and the **links** between them.
@@ -176,6 +176,20 @@ with receipts, not a benchmark.
176
176
 
177
177
  <img src="docs/okl-how-it-works.svg" alt="One repo records a lesson; every other repo is briefed on it before its next task. The store is typed, scoped and tagged; every verification stamp carries the check that earned it." width="100%">
178
178
 
179
+ ### How a briefing is actually built
180
+
181
+ `okl check` is a retrieval pipeline: seven stages turn a task sentence and the whole corpus
182
+ into the twelve records an agent reads. Two stages can drop a record, and only one of them
183
+ is entitled to — the distinction that this project got wrong once and measured its way out
184
+ of ([REPORT §4d](evals/REPORT.md)).
185
+
186
+ <img src="docs/okl-retrieval-pipeline.svg" alt="Seven stages: the corpus, a BM25 fetch of three times the limit, a scope gate, the exclusive applies_to filter, the inclusive tags-times-interests filter, a top-12 cutoff, bucketing by type, and rendering. Each stage shows how many records survive it." width="100%">
187
+
188
+ Every count in that diagram is **traced**, not transcribed — `docs/render_pipeline_diagram.py`
189
+ runs the real `store.search` and `core._in_scope` against a store seeded from `seed/`, and
190
+ `test_pipeline_diagram_is_current` fails if the committed render is not what the generator
191
+ produces today. Change a BM25 weight or a filter predicate and the diagram goes red with it.
192
+
179
193
  ### The mental model
180
194
 
181
195
  `okl` stores small, typed **notes** and the **links** between them.
@@ -57,3 +57,44 @@ seed-file comments ("eval-integrity lessons are org-scoped") and the scaffold's
57
57
  pipeline, not a language. The distinction the vocabulary already draws (stacks vs subjects)
58
58
  did not have a slot for "the language this is written in", and adding one is cheaper than
59
59
  overloading a stack tag whose meaning other repos depend on.
60
+ - 2026-09-03: **the "revisit when" condition above was met, and the vocabulary is now
61
+ extensible per store.** `KNOWN_TAGS` remains, but as the *floor* every store ships with
62
+ rather than the whole vocabulary: a store widens it by recording `Vocabulary` nodes,
63
+ whose title is the tag, and `Store.add_node` validates against floor ∪ declared.
64
+
65
+ The trigger was a language-support audit. Everything else in okl is language-agnostic —
66
+ drift asks git about path globs, verification runs whatever command you hand it, the
67
+ store holds text — and a Rust repo was driven end to end successfully except for one
68
+ thing: `okl record --tags rust` was refused, and the only remedy was to fork the package.
69
+ That is a hard stop on adoption for a tool whose whole claim is that a lesson learned in
70
+ one place reaches another.
71
+
72
+ What the closed vocabulary was *for* survives intact. It exists to catch the typo that
73
+ files a record where nobody looks, and closed-per-store still does: `rustt` is refused in
74
+ a store that declared `rust`. What changes is who is entitled to grow it — the org that
75
+ owns the store, rather than whoever can merge to this package.
76
+
77
+ A `Vocabulary` node is validated against the floor only. Otherwise the first declaration
78
+ in a fresh store would require the tag it is declaring.
79
+
80
+ **Not extended:** `STACK_TAGS`, which gates `applies_to`. Declaring `rust` makes it usable
81
+ as a subject tag, not as an applicability value. That is a narrower and rarer need — it
82
+ only matters for a lesson that is genuinely false off-stack — and §4h measured that the
83
+ filter `applies_to` feeds changes 0% of delivered briefing slots today. Extending the
84
+ exclusive mechanism without a measurement is exactly what §4d punished.
85
+ - 2026-09-17: `frontend` and `prose` added **to the floor**, when `emeraldleaf-dev` joined
86
+ the layer and every one of its records was rejected: markup and CSS defects, and rules
87
+ about writing. `react` was the nearest existing tag for the first group and is wrong for
88
+ the same reason `python-rag` was wrong for `python`. It is a stack tag, and that repo is a
89
+ hand-rolled Astro site with no UI framework. `frontend` names the subject those lessons are
90
+ actually about: markup whitespace semantics, CSS layout and responsive behaviour, browser
91
+ rendering. `prose` had no near-miss at all. Writing is governed in these repos the way code
92
+ is, with an em-dash budget and a claims-must-be-supported rule behind mechanical gates, and
93
+ a rule with a gate behind it is a rule the layer should carry.
94
+
95
+ The floor rather than a per-store `Vocabulary` declaration, now that the amendment above
96
+ makes both available. Neither tag is specific to one org: any repo that renders markup has
97
+ frontend lessons, and any repo that governs its writing has prose ones. The floor is for
98
+ subjects every store would otherwise have to declare for itself, which is the same reason
99
+ `messaging` and `python` are in it. Per-store declaration is for what an org's own stack
100
+ needs and nobody else's.
@@ -0,0 +1,89 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="980" height="994" viewBox="0 0 980 994" font-family="ui-sans-serif,-apple-system,Segoe UI,Helvetica,Arial,sans-serif">
2
+ <rect width="980" height="994" fill="#F5F3F0"/>
3
+ <text x="28" y="46" font-size="27" font-weight="700" fill="#1E1B18">How an okl briefing gets built</text>
4
+ <text x="28" y="72" font-size="14" fill="#4E4740">161 records and one task sentence become the 12 an agent reads. Two stages can drop a record; only one is entitled to.</text>
5
+ <text x="28" y="96" font-size="11.5" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" fill="#7C736A">GENERATED from the live pipeline - docs/render_pipeline_diagram.py</text>
6
+ <text x="28" y="112" font-size="11.5" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" fill="#7C736A">traced task: "Add an endpoint that returns a single order by id for the signed-in user."</text>
7
+ <text x="28" y="128" font-size="11.5" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" fill="#7C736A">repo interests: python, method, security, agent-safety, retrieval-design, eval-integrity, data-quality</text>
8
+ <rect x="28" y="154" width="924" height="90" fill="#FFFFFF" stroke="#DCD6CE"/>
9
+ <rect x="28" y="154" width="74" height="90" fill="#EBE7E1" stroke="#DCD6CE"/>
10
+ <text x="65" y="192" font-size="26" font-weight="700" fill="#B4531F" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" text-anchor="middle">0</text>
11
+ <text x="65" y="210" font-size="9" fill="#7C736A" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" text-anchor="middle" letter-spacing="1">STORE</text>
12
+ <text x="120" y="179" font-size="15" font-weight="600" fill="#1E1B18">The corpus</text>
13
+ <text x="120" y="198" font-size="12" fill="#4E4740">Every record carries scope (permission), tags (subject), applies_to (validity) and files</text>
14
+ <text x="120" y="213" font-size="12" fill="#4E4740">(what it governs). Nothing is ranked yet.</text>
15
+ <text x="120" y="224" font-size="11.5" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" fill="#1E1B18" font-weight="500">161 records</text>
16
+ <rect x="120" y="230" width="818" height="9" rx="2" fill="#EBE7E1"/><rect x="120" y="230" width="818" height="9" rx="2" fill="#B4531F"/>
17
+ <rect x="28" y="250" width="924" height="90" fill="#FFFFFF" stroke="#DCD6CE"/>
18
+ <rect x="28" y="250" width="74" height="90" fill="#EBE7E1" stroke="#DCD6CE"/>
19
+ <text x="65" y="288" font-size="26" font-weight="700" fill="#B4531F" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" text-anchor="middle">1</text>
20
+ <text x="65" y="306" font-size="9" fill="#7C736A" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" text-anchor="middle" letter-spacing="1">RANK</text>
21
+ <text x="120" y="275" font-size="15" font-weight="600" fill="#1E1B18">Fetch 3x more candidates than needed</text>
22
+ <text x="120" y="294" font-size="12" fill="#4E4740">SQLite FTS5, weighted title x8, body x4, symptom x4, fix x2. Tags are NOT indexed - a tag</text>
23
+ <text x="120" y="309" font-size="12" fill="#4E4740">can get a record excluded, never help it be found.</text>
24
+ <text x="120" y="320" font-size="11.5" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" fill="#1E1B18" font-weight="500">36 fetched</text>
25
+ <text x="938" y="320" font-size="11.5" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" fill="#7C736A" text-anchor="end">limit x 3</text>
26
+ <rect x="120" y="326" width="818" height="9" rx="2" fill="#EBE7E1"/><rect x="120" y="326" width="818" height="9" rx="2" fill="#B4531F"/>
27
+ <rect x="28" y="346" width="924" height="90" fill="#FFFFFF" stroke="#DCD6CE"/>
28
+ <rect x="28" y="346" width="74" height="90" fill="#EBE7E1" stroke="#DCD6CE"/>
29
+ <text x="65" y="384" font-size="26" font-weight="700" fill="#B4531F" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" text-anchor="middle">2</text>
30
+ <text x="65" y="402" font-size="9" fill="#7C736A" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" text-anchor="middle" letter-spacing="1">SCOPE</text>
31
+ <text x="120" y="371" font-size="15" font-weight="600" fill="#1E1B18">Permission - who may see this</text>
32
+ <text x="120" y="390" font-size="12" fill="#4E4740">A repo's own records pass first and unconditionally. org records continue. Another repo's</text>
33
+ <text x="120" y="405" font-size="12" fill="#4E4740">records stop here: the curation boundary.</text>
34
+ <text x="938" y="371" font-size="9.5" fill="#9B3B3B" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" font-weight="700" text-anchor="end" letter-spacing="0.8">DROPS</text>
35
+ <text x="120" y="416" font-size="11.5" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" fill="#1E1B18" font-weight="500">33 survive</text>
36
+ <text x="938" y="416" font-size="11.5" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" fill="#7C736A" text-anchor="end">3 dropped: other repos</text>
37
+ <rect x="120" y="422" width="818" height="9" rx="2" fill="#EBE7E1"/><rect x="120" y="422" width="750" height="9" rx="2" fill="#B4531F"/><rect x="870" y="422" width="68" height="9" rx="2" fill="#9B3B3B" opacity="0.4"/>
38
+ <rect x="28" y="442" width="924" height="90" fill="#FFFFFF" stroke="#DCD6CE"/>
39
+ <rect x="28" y="442" width="74" height="90" fill="#EBE7E1" stroke="#DCD6CE"/>
40
+ <text x="65" y="480" font-size="26" font-weight="700" fill="#B4531F" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" text-anchor="middle">3</text>
41
+ <text x="65" y="498" font-size="9" fill="#7C736A" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" text-anchor="middle" letter-spacing="1">VALID</text>
42
+ <text x="120" y="467" font-size="15" font-weight="600" fill="#1E1B18">applies_to - the only filter allowed to exclude</text>
43
+ <text x="120" y="486" font-size="12" fill="#4E4740">EXCLUSIVE. Set by hand on 31 of 161 records, only where a lesson is false off-stack. Unset</text>
44
+ <text x="120" y="501" font-size="12" fill="#4E4740">means anywhere, which is why it is safe.</text>
45
+ <text x="938" y="467" font-size="9.5" fill="#9B3B3B" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" font-weight="700" text-anchor="end" letter-spacing="0.8">DROPS</text>
46
+ <text x="120" y="512" font-size="11.5" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" fill="#1E1B18" font-weight="500">29 survive</text>
47
+ <text x="938" y="512" font-size="11.5" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" fill="#7C736A" text-anchor="end">4 dropped: off-stack</text>
48
+ <rect x="120" y="518" width="818" height="9" rx="2" fill="#EBE7E1"/><rect x="120" y="518" width="659" height="9" rx="2" fill="#B4531F"/><rect x="779" y="518" width="91" height="9" rx="2" fill="#9B3B3B" opacity="0.4"/>
49
+ <rect x="28" y="538" width="924" height="90" fill="#FFFFFF" stroke="#DCD6CE"/>
50
+ <rect x="28" y="538" width="74" height="90" fill="#EBE7E1" stroke="#DCD6CE"/>
51
+ <text x="65" y="576" font-size="26" font-weight="700" fill="#B4531F" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" text-anchor="middle">4</text>
52
+ <text x="65" y="594" font-size="9" fill="#7C736A" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" text-anchor="middle" letter-spacing="1">SUBJECT</text>
53
+ <text x="120" y="563" font-size="15" font-weight="600" fill="#1E1B18">tags x interests - one shared subject is enough</text>
54
+ <text x="120" y="582" font-size="12" fill="#4E4740">INCLUSIVE. Untagged always passes. The exclusive version was reverted (REPORT 4d): it hid 35</text>
55
+ <text x="120" y="597" font-size="12" fill="#4E4740">of 172 records and the briefed arm then reproduced a defect it dropped.</text>
56
+ <text x="938" y="563" font-size="9.5" fill="#4A6B3D" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" font-weight="700" text-anchor="end" letter-spacing="0.8">ANY-MATCH</text>
57
+ <text x="120" y="608" font-size="11.5" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" fill="#1E1B18" font-weight="500">21 survive</text>
58
+ <text x="938" y="608" font-size="11.5" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" fill="#7C736A" text-anchor="end">8 dropped: no shared subject</text>
59
+ <rect x="120" y="614" width="818" height="9" rx="2" fill="#EBE7E1"/><rect x="120" y="614" width="477" height="9" rx="2" fill="#B4531F"/><rect x="597" y="614" width="182" height="9" rx="2" fill="#9B3B3B" opacity="0.4"/>
60
+ <rect x="28" y="634" width="924" height="90" fill="#FFFFFF" stroke="#DCD6CE"/>
61
+ <rect x="28" y="634" width="74" height="90" fill="#EBE7E1" stroke="#DCD6CE"/>
62
+ <text x="65" y="672" font-size="26" font-weight="700" fill="#B4531F" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" text-anchor="middle">5</text>
63
+ <text x="65" y="690" font-size="9" fill="#7C736A" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" text-anchor="middle" letter-spacing="1">CUTOFF</text>
64
+ <text x="120" y="659" font-size="15" font-weight="600" fill="#1E1B18">Top 12, and the rest are counted not hidden</text>
65
+ <text x="120" y="678" font-size="12" fill="#4E4740">Full-text matching is permissive enough that a plausible task matches most of a mature</text>
66
+ <text x="120" y="693" font-size="12" fill="#4E4740">store. The number discarded travels with the result.</text>
67
+ <text x="938" y="659" font-size="9.5" fill="#9B3B3B" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" font-weight="700" text-anchor="end" letter-spacing="0.8">TRUNCATES</text>
68
+ <text x="120" y="704" font-size="11.5" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" fill="#1E1B18" font-weight="500">12 delivered</text>
69
+ <text x="938" y="704" font-size="11.5" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" fill="#7C736A" text-anchor="end">9 reported as trimmed</text>
70
+ <rect x="120" y="710" width="818" height="9" rx="2" fill="#EBE7E1"/><rect x="120" y="710" width="273" height="9" rx="2" fill="#B4531F"/><rect x="393" y="710" width="204" height="9" rx="2" fill="#9B3B3B" opacity="0.4"/>
71
+ <rect x="28" y="730" width="924" height="90" fill="#FFFFFF" stroke="#DCD6CE"/>
72
+ <rect x="28" y="730" width="74" height="90" fill="#EBE7E1" stroke="#DCD6CE"/>
73
+ <text x="65" y="768" font-size="26" font-weight="700" fill="#B4531F" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" text-anchor="middle">6</text>
74
+ <text x="65" y="786" font-size="9" fill="#7C736A" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" text-anchor="middle" letter-spacing="1">SHAPE</text>
75
+ <text x="120" y="755" font-size="15" font-weight="600" fill="#1E1B18">Bucket by type, then route to imperatives</text>
76
+ <text x="120" y="774" font-size="12" fill="#4E4740">Eight buckets: gates, defects, retractions, tombstones, threat prior-art, rules, vocabulary,</text>
77
+ <text x="120" y="789" font-size="12" fill="#4E4740">stale warnings. Stale demotes, never deletes. Then ARM / FIX / AVOID.</text>
78
+ <text x="120" y="800" font-size="11.5" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" fill="#1E1B18" font-weight="500">gates -&gt; defects+rules</text>
79
+ <text x="938" y="800" font-size="11.5" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" fill="#7C736A" text-anchor="end">-&gt; retractions -&gt; tombstones</text>
80
+ <rect x="28" y="826" width="924" height="90" fill="#FFFFFF" stroke="#DCD6CE"/>
81
+ <rect x="28" y="826" width="74" height="90" fill="#EBE7E1" stroke="#DCD6CE"/>
82
+ <text x="65" y="864" font-size="26" font-weight="700" fill="#B4531F" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" text-anchor="middle">7</text>
83
+ <text x="65" y="882" font-size="9" fill="#7C736A" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" text-anchor="middle" letter-spacing="1">RENDER</text>
84
+ <text x="120" y="851" font-size="15" font-weight="600" fill="#1E1B18">Three surfaces, one pipeline</text>
85
+ <text x="120" y="870" font-size="12" fill="#4E4740">agent | actions | json. A zero-match result carries the store size, so 'no rules apply' can</text>
86
+ <text x="120" y="885" font-size="12" fill="#4E4740">never be confused with 'the store is empty'.</text>
87
+ <text x="120" y="896" font-size="11.5" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" fill="#1E1B18" font-weight="500">hook | MCP | CI</text>
88
+ <text x="28" y="968" font-size="11" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" fill="#7C736A">src/okl/core.py check() / _in_scope() . src/okl/store.py search() . regenerate: python3 docs/render_pipeline_diagram.py</text>
89
+ </svg>
@@ -0,0 +1,55 @@
1
+ # The elegant change was the wrong one
2
+
3
+ *Part 4 of 3: The loop that learns. What happened when the method was pointed at the tool that implements it.*
4
+
5
+ I built a thing that tells you to verify your claims mechanically, then spent a day running it against itself. It found nineteen defects. This is about what they had in common, and about the one that survived every check I had.
6
+
7
+ ## Everything that broke had never been run
8
+
9
+ The MCP server could not start. The library it depends on had renamed the class it imports, so `pip install` produced a server that failed on load, and the error handler told you to install the extra you had just installed.
10
+
11
+ The Postgres path had never touched Postgres. The schema was right, the queries were right, and the parity test I wrote to prove it opened with `DELETE FROM node` against whatever database you pointed it at. Anyone running it against their real store to check parity would have lost everything in it.
12
+
13
+ The shared service, deployed, answered 500 to every request. The ASGI entrypoint documented in its own docstring resolved to a module-level `None`. The process started, bound its port, and passed a liveness check that only asked whether something was listening.
14
+
15
+ And the token that gated writes did not gate reads, so `GET /nodes` handed the entire store to anyone who found the URL. For a knowledge layer that is not a minor leak. The store is a catalogue of the places an organisation already knows it is weak.
16
+
17
+ Four failures, one shape. Each lived in a surface nobody had ever exercised. Not badly written, not under-reviewed. Unrun. The defects clustered exactly where you would predict if you were being honest with yourself, which is the least surprising distribution in software and somehow always a surprise.
18
+
19
+ ## The one that was carefully reasoned
20
+
21
+ Late in the day I found a real problem. Briefings in a Python repository were full of .NET rules. The vocabulary already separates *stacks* from *subjects*, so the fix looked obvious: a record naming a stack should only reach a repository that declared that stack, while subject tags stay permissive. One universal subject was rescuing every off-stack record into every project.
22
+
23
+ I implemented it. It passed its tests. It passed the architecture review. The reasoning was drawn from the system's own documented distinction, and I wrote three paragraphs of commentary explaining why it was correct.
24
+
25
+ Then the A/B measured it, and the briefed arm posted its worst result across four runs.
26
+
27
+ The cause took ten minutes to find and was not subtle once seen. One rule reads *"in-memory rate limiters silently weaken to N times the limit at N instances."* It is tagged `dotnet`. It is also true of every runtime ever written. The tag records where the lesson was **found**, not where it **applies**, because tags get assigned in bulk when a corpus is imported from a codebase. My filter read provenance as applicability and hid 35 of the 172 records in the store at the time, including an IDOR rule, a data-transposition defect, and the rate-limiter rule itself. The generated code then reproduced the exact defect the hidden rule described, on a task where the unaided arm reproduced nothing.
28
+
29
+ Most of the records tagged `dotnet` in that store are portable engineering lessons. I will not put a percentage on it, because the only way I have to classify them is keyword matching on their titles, and the answer moves by twenty points depending on which words I pick. That is the sort of number this series is about not quoting. The unambiguous cases are enough: an IDOR rule, JWT clock skew, fanout exchanges discarding unroutable messages, time-ordered primary keys. None of those is about .NET, and no amount of reading the filter's code would have told me, because the code was doing precisely what I designed it to do.
30
+
31
+ ## Reasoning is not evidence, and review is not measurement
32
+
33
+ That change had everything except a measurement. It had a rationale, a test suite, a passing review, and a comment block. What it did not have was a number, and the number was the only thing that disagreed with it.
34
+
35
+ The uncomfortable part is the ranking. Of the nineteen defects, the four unrun surfaces were the easiest to find, because running them was enough. The carefully argued one needed a controlled experiment to catch, and I would have shipped it as an improvement.
36
+
37
+ There is a corollary I like less. Reverting was correct, but doing it in that order was not. The change should never have run without a written prediction and, more usefully, a list of which cases could plausibly lose a record they depend on. That list would have named the failing task before the experiment started, for free.
38
+
39
+ ## The cheapest check was the one nobody was running
40
+
41
+ Whether a record reaches a briefing is deterministic. It needs no model, no samples, and no judge. It is a query.
42
+
43
+ Two separate regressions were each discovered only after a full run: twenty-five minutes and forty-eight model calls, to learn something a database could have answered instantly. So now a pre-flight asks it directly before anything is spent: for each case, is the rule it exists to test actually present in the retrieval it will receive?
44
+
45
+ The first time it ran, it found that two of eight cases had not been receiving their rule at all. One was filtered out by a tag the host repository never declared. The other had been silently dropped by a relevance cutoff added weeks earlier, in a change whose own report concluded it caused no retrieval miss. That conclusion had been drawn from outcomes rather than from asking the question. The case still looked healthy, because something else in the briefing happened to prevent the defect. It was never evidence for the rule it was filed under.
46
+
47
+ That correction is in the report now, above the section it corrects.
48
+
49
+ ## What I would take from this
50
+
51
+ Untested surfaces contain defects in proportion to how untested they are, and that is boring and cheap to fix: run them. The expensive lesson is the second one.
52
+
53
+ The changes most likely to be wrong are the ones you can argue for best. A change with a clean rationale, a green suite, and an approving reviewer has passed every filter except contact with reality, and the more elegant the argument, the more of your judgment it has already borrowed. So decide in advance what would falsify it, write that down where you cannot revise it later, and make sure the cheap deterministic check runs before the expensive stochastic one.
54
+
55
+ None of that is a new idea. What was new to me was watching it fail on the tool built to enforce it, in a session where I was actively looking.