@agentskit/doc-bridge 1.7.45 → 1.9.0

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 (213) hide show
  1. package/CHANGELOG.md +465 -0
  2. package/CONTRIBUTING.md +23 -0
  3. package/README.md +87 -40
  4. package/SECURITY.md +7 -0
  5. package/action.yml +1 -1
  6. package/bin/ak-docs.js +2 -2
  7. package/bin/ak-verify.js +13 -7
  8. package/dist/cli/program.d.ts +3 -1
  9. package/dist/cli/program.js +15861 -6223
  10. package/dist/cli/program.js.map +1 -1
  11. package/dist/config/index.d.ts +1 -1
  12. package/dist/config/index.js +86 -6
  13. package/dist/config/index.js.map +1 -1
  14. package/dist/{index-BUL0q7s8.d.ts → index-Beor6Yhi.d.ts} +133 -1
  15. package/dist/index.d.ts +9871 -1062
  16. package/dist/index.js +16126 -6125
  17. package/dist/index.js.map +1 -1
  18. package/docs/MARKETPLACE.md +1 -1
  19. package/docs/PRD-documentation-efficiency-study.md +406 -0
  20. package/docs/PRD-knowledge-retrieval-and-enrichment.md +466 -0
  21. package/docs/adr/0002-documentation-audit-boundary.md +22 -0
  22. package/docs/adr/0003-study-protocol-and-historical-evidence.md +40 -0
  23. package/docs/adr/0004-controlled-study-runner.md +25 -0
  24. package/docs/adr/0005-documentation-quality-and-criticality.md +20 -0
  25. package/docs/adr/0006-registry-semantic-grounding.md +20 -0
  26. package/docs/adr/0007-longitudinal-study-metrics.md +21 -0
  27. package/docs/adr/0008-study-verification-boundary.md +21 -0
  28. package/docs/adr/0009-study-provider-cli-adapter.md +25 -0
  29. package/docs/agent-corpus/INDEX.md +12 -1
  30. package/docs/agent-corpus/OVERVIEW.md +25 -0
  31. package/docs/agent-corpus/chat.md +5 -1
  32. package/docs/agent-corpus/cli.md +16 -0
  33. package/docs/agent-corpus/conformance.md +12 -0
  34. package/docs/agent-corpus/doc-bridge.md +47 -0
  35. package/docs/agent-corpus/doctor.md +8 -0
  36. package/docs/agent-corpus/gates.md +4 -0
  37. package/docs/agent-corpus/mcp.md +13 -0
  38. package/docs/agent-corpus/memory.md +4 -0
  39. package/docs/agent-corpus/query.md +33 -0
  40. package/docs/bench/README.md +122 -0
  41. package/docs/bench/retrieval-baseline-v1.json +28 -0
  42. package/docs/bench/retrieval-suite-v1.json +1033 -0
  43. package/docs/chat-and-rag.md +3 -2
  44. package/docs/for-agents.md +9 -1
  45. package/docs/getting-started.md +4 -11
  46. package/docs/guides/gate-ci.md +11 -1
  47. package/docs/guides/install-and-run.md +9 -65
  48. package/docs/index.md +22 -1
  49. package/docs/knowledge-engine-runbook.md +38 -3
  50. package/docs/landing/assets/context-payload-reduction.svg +21 -0
  51. package/docs/landing/assets/controlled-ab-comparison.svg +30 -0
  52. package/docs/landing/index.html +119 -5
  53. package/docs/loop-workflow.md +117 -0
  54. package/docs/mcp.md +6 -1
  55. package/docs/parity/public-claims-v1.json +145 -0
  56. package/docs/playbook/doc-bridge-pattern.md +1 -1
  57. package/docs/query.md +90 -2
  58. package/docs/recipes/index-pipeline.md +1 -1
  59. package/docs/schemas/agent-handoff-v1.md +15 -0
  60. package/docs/schemas/doc-bridge-index-v1.md +65 -0
  61. package/docs/spec/benchmark-v1.md +33 -1
  62. package/docs/spec/cli.md +30 -10
  63. package/docs/spec/config-v1.md +147 -8
  64. package/docs/spec/documentation-audit-v1.md +61 -0
  65. package/docs/spec/enrichment-overlay-v1.md +241 -0
  66. package/docs/spec/graph-signals-v1.md +92 -0
  67. package/docs/spec/incremental-scan-v1.md +102 -0
  68. package/docs/spec/markdown-analyzer-v1.md +73 -0
  69. package/docs/spec/mcp-knowledge-tools-v1.md +147 -0
  70. package/docs/spec/measured-enrichment-v1.md +229 -0
  71. package/docs/spec/public-parity-v1.md +119 -0
  72. package/docs/spec/registry-agents.md +6 -0
  73. package/docs/spec/render-v1.md +122 -0
  74. package/docs/spec/retrieval-index-v1.md +164 -0
  75. package/docs/spec/study-metrics-v1.md +58 -0
  76. package/docs/spec/study-protocol-v1.md +46 -0
  77. package/docs/spec/study-provider-cli-v1.md +116 -0
  78. package/docs/spec/study-runner-v1.md +35 -0
  79. package/docs/spec/study-task-suite-v1.md +41 -0
  80. package/docs/spec/study-verification-v1.md +40 -0
  81. package/docs/study/README.md +84 -0
  82. package/docs/study/ab-adjudicated-cost-analysis-v1.md +29 -0
  83. package/docs/study/ab-adjudicated-cost-plan-v1.json +33 -0
  84. package/docs/study/ab-adjudicated-cost-plan-v2-v1.json +33 -0
  85. package/docs/study/ab-adjudicated-cost-result-v1.json +80 -0
  86. package/docs/study/ab-baseline-analysis-v1.md +21 -0
  87. package/docs/study/ab-baseline-plan-v1.json +33 -0
  88. package/docs/study/ab-baseline-recovery-plan-v1.json +33 -0
  89. package/docs/study/ab-baseline-result-v1.json +79 -0
  90. package/docs/study/documentation-audit-round-2026-08-31.json +183 -0
  91. package/docs/study/historical-evidence-v1.json +252 -0
  92. package/docs/study/observation-ledger-v1.json +30632 -0
  93. package/docs/study/phase3-task-coverage-v1.json +34 -0
  94. package/docs/study/phase4-public-pilot-ledger-v1.json +1344 -0
  95. package/docs/study/phase4-public-pilot-result-v1.json +52 -0
  96. package/docs/study/phase4-public-pilot-run-plan-v1.json +26 -0
  97. package/docs/study/phase4-public-pilot-task-suite-v1.json +71 -0
  98. package/docs/study/pilot-round-2026-08-31.json +46 -0
  99. package/docs/study/protocol-v1.json +90 -0
  100. package/docs/study/publication-gate-v1.md +45 -0
  101. package/docs/study/quality-scorecard-cycle-plan.md +545 -0
  102. package/docs/study/quality-scorecard-v1.json +38 -0
  103. package/docs/study/round-1-adjudicated-smoke-v1.json +30642 -0
  104. package/docs/study/round-1-instrumentation-plan-v1.md +39 -0
  105. package/docs/study/round-2-expanded-adjudication-v1.json +91 -0
  106. package/docs/study/round-2-expanded-validation-v1.md +58 -0
  107. package/docs/study/round-3-evidence-contract-v1.json +75 -0
  108. package/docs/study/round-3-evidence-contract-v1.md +57 -0
  109. package/docs/study/round-4-confirmation-v1.json +75 -0
  110. package/docs/study/round-4-confirmation-v1.md +55 -0
  111. package/docs/study/run-plan-v1.json +33 -0
  112. package/docs/study/semantic-adjudication-cycle-8.md +20 -0
  113. package/docs/study/task-suite-v1.json +96 -0
  114. package/docs/study/token-efficiency-plan-v1.md +337 -0
  115. package/docs/study/token-efficiency-protocol-v2.json +62 -0
  116. package/docs/study/verification-binding-v1.json +27 -0
  117. package/docs/validation-cycle-plan.md +14 -0
  118. package/docs/verification-harness.md +11 -6
  119. package/ecosystem-claims.json +2 -2
  120. package/ecosystem-upstream.json +2 -2
  121. package/ecosystem.json +4 -4
  122. package/mcpb/manifest.json +9 -1
  123. package/package.json +25 -6
  124. package/scripts/check-ecosystem-upstream.mjs +34 -6
  125. package/skills/doc-bridge-handoff/fixtures/synthetic-repo/docs/for-agents/packages/payments.md +7 -0
  126. package/skills/doc-bridge-handoff/scripts/resolve-handoff.mjs +1 -1
  127. package/src/agents/registry-adapter.ts +192 -24
  128. package/src/audit/documentation.ts +513 -0
  129. package/src/bench/baseline.ts +198 -0
  130. package/src/bench/overlay-delta.ts +139 -0
  131. package/src/bench/retrieval.ts +319 -0
  132. package/src/budget/compile.ts +91 -0
  133. package/src/budget/sections.ts +70 -0
  134. package/src/cli/program.ts +684 -74
  135. package/src/cli/usage.ts +71 -0
  136. package/src/config/defaults.ts +1 -0
  137. package/src/config/index.ts +4 -0
  138. package/src/config/schema.ts +117 -2
  139. package/src/conformance/documentation-standard-v1.ts +8 -6
  140. package/src/discovery/areas.ts +182 -0
  141. package/src/discovery/documentation.ts +211 -5
  142. package/src/discovery/identity.ts +24 -0
  143. package/src/discovery/incremental.ts +314 -0
  144. package/src/discovery/inputs.ts +110 -0
  145. package/src/discovery/markdown.ts +481 -0
  146. package/src/discovery/repository.ts +484 -101
  147. package/src/doctor/run-doctor.ts +244 -12
  148. package/src/enrich/approvals.ts +190 -0
  149. package/src/enrich/cache.ts +93 -0
  150. package/src/enrich/context-pack.ts +272 -0
  151. package/src/enrich/overlay.ts +255 -0
  152. package/src/enrich/review.ts +106 -0
  153. package/src/enrich/stage.ts +374 -0
  154. package/src/enrich/stats.ts +100 -0
  155. package/src/enrich/validate.ts +410 -0
  156. package/src/federation/llms.ts +2 -4
  157. package/src/findings/report.ts +103 -0
  158. package/src/graph/build.ts +356 -0
  159. package/src/graph/memory.ts +208 -0
  160. package/src/index-builder/build-handoffs.ts +22 -11
  161. package/src/index-builder/build-index.ts +132 -3
  162. package/src/index-builder/llms-txt.ts +48 -8
  163. package/src/index-builder/project-corpus.ts +111 -0
  164. package/src/index.ts +630 -2
  165. package/src/lib/fuzzy-match.ts +235 -0
  166. package/src/mcp/knowledge.ts +554 -0
  167. package/src/mcp/server.ts +113 -18
  168. package/src/metrics/benchmark.ts +21 -0
  169. package/src/parity/check.ts +309 -0
  170. package/src/parity/claims.ts +259 -0
  171. package/src/parity/resolve.ts +160 -0
  172. package/src/query/handoff.ts +326 -0
  173. package/src/query/load-index.ts +53 -1
  174. package/src/query/query.ts +92 -59
  175. package/src/query/search.ts +289 -92
  176. package/src/query/text.ts +155 -0
  177. package/src/reconciliation/reconcile.ts +101 -10
  178. package/src/render/data.ts +356 -0
  179. package/src/render/engine.ts +398 -0
  180. package/src/render/generated.ts +77 -0
  181. package/src/render/render.ts +209 -0
  182. package/src/render/template-source.ts +52 -0
  183. package/src/render/templates.ts +289 -0
  184. package/src/report/html.ts +2 -2
  185. package/src/retrieval/bm25.ts +161 -0
  186. package/src/retrieval/project.ts +495 -0
  187. package/src/retrieval/rank.ts +383 -0
  188. package/src/retrieval/weights.ts +39 -0
  189. package/src/retriever/doc-bridge-retriever.ts +100 -15
  190. package/src/rules/engine.ts +30 -10
  191. package/src/schemas/agent-handoff.ts +56 -0
  192. package/src/schemas/budget.ts +37 -0
  193. package/src/schemas/doc-bridge-index.ts +53 -2
  194. package/src/schemas/enrichment.ts +369 -0
  195. package/src/schemas/json-schemas.ts +39 -2
  196. package/src/schemas/knowledge.ts +14 -1
  197. package/src/schemas/retrieval-index.ts +152 -0
  198. package/src/shims/graphology.d.ts +91 -0
  199. package/src/study/adjudication.ts +196 -0
  200. package/src/study/execution.ts +350 -0
  201. package/src/study/expectations.ts +219 -0
  202. package/src/study/metrics.ts +467 -0
  203. package/src/study/protocol.ts +271 -0
  204. package/src/study/provider-cli.ts +115 -0
  205. package/src/study/provider-telemetry.ts +47 -0
  206. package/src/study/quality-scorecard.ts +164 -0
  207. package/src/study/runner.ts +461 -0
  208. package/src/study/task-suite.ts +321 -0
  209. package/src/study/verification.ts +134 -0
  210. package/src/validate.ts +1 -4
  211. package/src/version.ts +1 -1
  212. package/src/workflow/engine.ts +36 -11
  213. package/scripts/verification-harness.mjs +0 -482
@@ -0,0 +1,92 @@
1
+ ---
2
+ title: Graph signals v1
3
+ description: What Doc Bridge computes from the repository graph, what each signal means, and what it deliberately does not claim.
4
+ ---
5
+
6
+ # Graph signals v1
7
+
8
+ The knowledge snapshot is a graph, and some questions are only answerable as graph questions: which
9
+ document is the entry point, which module every change reaches through, which directories move
10
+ together, where the imports run in a circle.
11
+
12
+ These are computed with [graphology](https://graphology.github.io) from the snapshot, on demand.
13
+ The graph is a working structure: it is never serialised, `DiscoverySnapshotV1` does not change, and
14
+ graphology's own format never reaches disk. Nodes and edges are inserted in sorted order and every
15
+ score is rounded before it is returned, so two runs over the same snapshot agree exactly — including
16
+ after the input order is shuffled.
17
+
18
+ ## The signals
19
+
20
+ | Signal | Computed from | Over |
21
+ | --- | --- | --- |
22
+ | **Canonicality** | PageRank | `links-to` and `covers` |
23
+ | **Centrality** | betweenness, normalized | `imports` and `re-exports` |
24
+ | **Proximity** | bounded shortest path | imports, documentation, `mentions`, `depends-on` |
25
+ | **Import cycles** | depth-first search | `imports` and `re-exports` |
26
+ | **Area suggestions** | seeded Louvain communities | `imports` and `re-exports` |
27
+
28
+ External and unresolved endpoints are left out unless a caller asks for them: a dependency on a
29
+ third-party package is a fact about the repository, not a part of its architecture.
30
+
31
+ **Canonicality** answers where a reader should start. A page many documents link to, or that covers
32
+ many entities, outranks a leaf note nothing points at — however recently the leaf was edited.
33
+
34
+ **Centrality** answers how much of the dependency structure runs through one module. It is a review
35
+ signal: a change there reaches further than its diff suggests. It is *not* a statement about runtime
36
+ availability, and the diagnostic says so, because "single point of failure" is a claim about
37
+ deployment that a static import graph cannot make.
38
+
39
+ **Proximity** answers how many hops apart two entities are, bounded — three by default. Unbounded
40
+ proximity is not useful: at ten hops everything is related to everything. `contains` is excluded on
41
+ purpose. It is hierarchy, and including it puts every module in an area two hops from every other
42
+ one, which is true and tells a reader nothing.
43
+
44
+ **Import cycles** are reported with every edge that forms them. `graphology-dag` answers whether a
45
+ cycle exists; reporting one needs the path, because a diagnostic whose loop a reader cannot trace is
46
+ a claim rather than a finding. The search is bounded in count, and the cycle is rotated so the
47
+ lexicographically smallest node comes first — the same cycle found from two different entry points
48
+ is one finding.
49
+
50
+ ## Area suggestions are suggestions
51
+
52
+ A Louvain community is a hypothesis: these modules move together, so perhaps they are one unit. It
53
+ never becomes an [area](./config-v1.md#analysisareas-optional) on its own. An area is derived from
54
+ the repository's own structure or declared by a human, and a clustering algorithm is neither.
55
+
56
+ Suggestions travel as `coverage` entries with `analyzer: graph`, `scope: area-suggestion:<path>` and
57
+ status `not-analyzed` — the honest status, because the clustering ran but whether the cluster is an
58
+ area is a question nobody has answered. A directory that is already an area produces no suggestion.
59
+ Community detection draws from a seeded generator, so a suggestion is reproducible rather than a
60
+ different guess each run.
61
+
62
+ ## Reading the graph as memory
63
+
64
+ ```ts
65
+ import { createDocBridgeGraphMemory } from '@agentskit/doc-bridge'
66
+
67
+ const graph = createDocBridgeGraphMemory(snapshot)
68
+ await graph.getNode('module:src/mcp/server.ts')
69
+ await graph.findEdges({ label: 'covers', to: 'area:src/mcp' })
70
+ await graph.neighbors('module:src/mcp/server.ts', { depth: 2 })
71
+ ```
72
+
73
+ This satisfies `GraphMemory` from `@agentskit/memory`, so an agent built on AgentsKit walks
74
+ repository structure with the same three calls it uses for its own memory. A second argument layers
75
+ extra entities and relations over the observation, in the snapshot's own vocabulary, so an approved
76
+ enrichment overlay plugs in unchanged.
77
+
78
+ Writes are accepted and kept in process. The snapshot is an observation and a caller cannot edit it,
79
+ so `upsertNode` lands in a working layer above the projection and `deleteNode` masks rather than
80
+ erases — an agent can annotate what it is exploring without any of it being mistaken for something
81
+ the repository said. `clear()` drops the working layer and leaves the projection intact.
82
+
83
+ ## What consumes them
84
+
85
+ `centrality-risk` in the rules engine is betweenness now. It used to count how many
86
+ undocumented-relation findings were attached to an entity, which measures documentation debt and
87
+ calls it architecture: a module every import path runs through scored zero if it happened to be
88
+ documented. Without a graph the rule reports nothing at all, which is better than reporting the
89
+ wrong thing under a name people act on.
90
+
91
+ The rule's threshold reads as a rank when it is 1 or more — `3` means "flag the three most central
92
+ entities" — and as a minimum betweenness when it is below 1.
@@ -0,0 +1,102 @@
1
+ ---
2
+ title: Incremental scan v1
3
+ description: How Doc Bridge hashes each file-backed entity, when a second scan may reuse one, and what it refuses to reuse.
4
+ ---
5
+
6
+ # Incremental scan v1
7
+
8
+ Every `module`, `document` and `package` entity carries the hash of its file in its first evidence
9
+ item. An `external` entity carries none: it is a name in a manifest, not a file on disk.
10
+
11
+ ```json
12
+ {
13
+ "id": "module:src/query/search.ts",
14
+ "kind": "module",
15
+ "evidence": [{ "source": "code", "path": "src/query/search.ts", "contentHash": "…" }]
16
+ }
17
+ ```
18
+
19
+ `EvidenceSchema.contentHash` had existed since the first schema and discovery never filled it, so
20
+ every cache and every overlay could be keyed only on "the whole repository changed" — which is true
21
+ between any two commits and therefore useless. With a hash per file, a consumer can expire one
22
+ entry, and a second scan can skip the expensive part: the TypeScript parse and the Markdown parse,
23
+ where nearly all of discovery's time goes.
24
+
25
+ A document's hash is taken after a leading byte-order mark is stripped. A mark is not content: a
26
+ file that only gained one parses to the same tree, and should not invalidate anything.
27
+
28
+ ## Reuse
29
+
30
+ ```ts
31
+ const cold = discoverRepository({ root, config })
32
+ const fast = discoverRepository({ root, config, previous: cold })
33
+ ```
34
+
35
+ `previous` is an offer, not an instruction. Discovery reuses an entity only when reuse cannot change
36
+ the answer, and two different things can change it:
37
+
38
+ - **An entity's own fields depend on its own bytes.** A hash match is enough.
39
+ - **A relation depends on what else exists.** A module importing `./new.js` resolved to nothing
40
+ before that file was added and resolves to a module after; a document mentioning `rank` points at
41
+ whichever module declares it. So relation reuse also requires that the universe the references
42
+ resolve against is identical.
43
+
44
+ Two fingerprints capture that universe, both derived from the previous snapshot rather than stored
45
+ in it — everything they cover is in the snapshot already, and a stored fingerprint is one more
46
+ thing that can be stale or forged.
47
+
48
+ | Fingerprint | Covers | Gates |
49
+ | --- | --- | --- |
50
+ | Module universe | module paths, packages, compiler options | reuse of a module's relations |
51
+ | Resolution | the module universe, plus document paths, area paths and which module declares each exported symbol | reuse of a document |
52
+
53
+ A reused entity's relations are replayed against the entity set the scan is producing. An edge
54
+ whose internal target is gone is dropped rather than carried: the file it pointed at was renamed or
55
+ deleted, and a graph that keeps the edge is lying about the repository. An external or unresolved
56
+ endpoint is re-added instead, because such an entity is in the snapshot only because something
57
+ referenced it, and the thing that referenced it is exactly what was reused.
58
+
59
+ ## What is refused outright
60
+
61
+ A hash says a file has not changed. It says nothing about whether this code would still read it the
62
+ same way — an analyzer that learns to record a document's headings produces different entities from
63
+ identical bytes, and a configuration change moves area boundaries and runtime-wiring detection. So
64
+ the whole snapshot is refused unless it was produced by this `pipelineVersion`, these
65
+ `analyzerVersions` and this `configurationHash`, and a snapshot that does not declare all three is
66
+ refused as well. Trusting an undeclared input with a repository scan is how a cache becomes a
67
+ source of wrong answers.
68
+
69
+ A cache that is only usually right is worse than no cache. Reuse either produces the snapshot a
70
+ cold scan would produce, or it does not happen.
71
+
72
+ A reused entity also replays the per-file `coverage` its analyzer produced, because the aggregate
73
+ entries are derived from those rather than stored. An aggregate that cannot be rebuilt from what
74
+ the snapshot carries is an aggregate a fast scan gets wrong: a fact that lives only in a local
75
+ variable during a parse is a fact the next scan cannot replay. `dynamic-imports:<path>` and
76
+ `runtime-wiring:<path>` therefore record every observed load and wiring call — `complete` when the
77
+ target is statically known, `not-analyzed` when it is not.
78
+
79
+ ## The run explains itself
80
+
81
+ A run that finishes in a tenth of the time has to be able to say why, or nobody can tell a working
82
+ cache from a broken scan. One `coverage` entry reports it:
83
+
84
+ ```json
85
+ {
86
+ "analyzer": "repository",
87
+ "scope": "reused-entities",
88
+ "status": "complete",
89
+ "reason": "Reused 102 entities and skipped 102 of 102 parse(s). Nothing needed re-parsing."
90
+ }
91
+ ```
92
+
93
+ `status` is `complete` when everything reusable was reused, `partial` when reuse was refused — the
94
+ reason then names what changed — and `not-applicable` when there was no previous snapshot to reuse.
95
+
96
+ This entry is the one part of a snapshot that describes the *run* rather than the repository. The
97
+ entities, the relations and every other coverage entry are byte-identical to a cold scan's, which
98
+ is what the tests assert; the snapshot's own `contentHash` covers this entry too, so a warm scan
99
+ and a cold scan of the same tree hash differently. That is why the CLI does not yet pass a previous
100
+ snapshot: the artifacts it writes are compared across runs, and the caching layer has to decide
101
+ what a report keys on before a fast scan starts feeding it. `contentHash` and `sourceRevision`
102
+ semantics are otherwise untouched.
@@ -0,0 +1,73 @@
1
+ ---
2
+ title: Markdown analyzer v1
3
+ description: What Doc Bridge reads from a Markdown document, and the relations it observes from prose.
4
+ ---
5
+
6
+ # Markdown analyzer v1
7
+
8
+ Documentation is parsed with remark (CommonMark plus GFM), not with regular expressions, so a
9
+ document's own prose becomes evidence. Every relation below is `observed` and carries the file and
10
+ line the claim was made on.
11
+
12
+ | Relation | From → to | Observed from |
13
+ | --- | --- | --- |
14
+ | `links-to` | document → document | a relative link that resolves to a scanned document |
15
+ | `mentions` | document → module or package | inline code or link text equal to a scanned path or a package name |
16
+ | `mentions-symbol` | document → module | inline code equal to an exported name of exactly one module |
17
+ | `covers` | document → anything | a `docbridge` declaration, unchanged |
18
+
19
+ A symbol resolves to the module that **declares** it, never to a barrel that re-exports it. When
20
+ two modules declare the same name the reference resolves to neither: the tokens and their lines
21
+ are reported as a `markdown` coverage note instead, because sending an agent to one of two
22
+ possible definitions is worse than sending it nowhere. The same rule governs near-misses — an
23
+ unresolved path-shaped reference is matched with Jaro-Winkler and accepted only at 0.92 or above
24
+ *and* with a single candidate, recorded as `metadata.confidence: "fuzzy"`.
25
+
26
+ Document entities gain what the parser can see: `title`, headings to depth three with their
27
+ lines, a bounded `summary`, `wordCount`, the frontmatter subset (`type`, `audience`, `owner`,
28
+ `lifecycle`, `tier`), any generated regions, and the file's `contentHash` on its evidence. A
29
+ document declaring `audience` overrides the path heuristic that classifies it; `type` overrides it
30
+ only when it names an audience, since in practice `type` names a document kind.
31
+
32
+ A document referencing more than 64 entities records `evidenceTruncated` and a coverage note. An
33
+ index page's sixty-fifth link is not knowledge, and an unbounded list is not evidence.
34
+
35
+ ## Generated regions
36
+
37
+ ```markdown
38
+ <!-- doc-bridge:generated hash=8f79947 -->
39
+ …generator output…
40
+ <!-- /doc-bridge:generated -->
41
+ ```
42
+
43
+ Mentions inside a generated region are ignored, so Doc Bridge never reads its own output back in
44
+ as evidence about the repository. An unclosed marker owns the rest of the file. The regions are
45
+ recorded on the document entity, which is what lets the audit report a manual edit inside one.
46
+
47
+ ## The `docbridge` block
48
+
49
+ The block is real YAML validated by a schema, so quoted lists, flow mappings, anchors and
50
+ multi-line strings work as they do in every other tool. Schema violations report the field:
51
+ `docbridge.relations.0: Unrecognized key: note`. Each `DOCBRIDGE_*` code is preserved — a
52
+ repository failing its build on one keeps failing on the same one — and a block YAML cannot read
53
+ at all falls back to the line-oriented scanner, because on a mangled block a diagnostic per line
54
+ helps the author more than a single parser error.
55
+
56
+ ## The cost of a near-miss
57
+
58
+ Near-miss resolution is the analyzer's only super-linear step: every unresolved path-shaped
59
+ reference is a query against every document, module and area path in the repository. On a monorepo
60
+ of 4 100 documents and 9 240 modules the naive form of that — rebuilding the candidate list per
61
+ document, then computing Jaro-Winkler against each candidate — did not finish.
62
+
63
+ Two bounds make it cheap, and both are bounds on Jaro's match count `m` rather than heuristics.
64
+ `m` cannot exceed the shorter string, so `jaro ≤ (m/|a| + m/|b| + 1) / 3`; with the prefix bonus
65
+ bounded by `jw ≤ 0.6·jaro + 0.4`, a threshold of 0.92 admits only candidates whose length is
66
+ within 0.6× to 1.67× the query's. And `m` cannot exceed the number of characters the two strings
67
+ share, which one pass over a 128-slot count vector answers, where a similarity computation costs a
68
+ pass over one string per character of the other.
69
+
70
+ Both only ever over-estimate `m`, so a candidate they skip could not have matched: the index is a
71
+ speed-up with no effect on results, tied scores included. `MarkdownResolution.pathIndex` carries it,
72
+ built once per run; an analyzer called without one builds its own and produces the same output more
73
+ slowly.
@@ -0,0 +1,147 @@
1
+ ---
2
+ title: MCP knowledge tools v1
3
+ description: One search and one lookup that answer inside a declared token budget, findings in the shape the ecosystem consumes, and a doctor that measures what retrieval can see.
4
+ ---
5
+
6
+ # MCP knowledge tools v1
7
+
8
+ An agent about to edit `src/mcp` used to need four calls — a search, a handoff, the diagnostics,
9
+ the relations — and got back payloads whose size it could only estimate as `contextBytes / 4`.
10
+ Two tools now answer that question in one bounded response, the existing tools stay as they are,
11
+ and the doctor stops reporting a health it did not measure.
12
+
13
+ ## `knowledge.search`
14
+
15
+ ```json
16
+ { "query": "where do I add a new MCP tool", "kinds": ["document", "area"], "limit": 5, "explain": true, "budgetTokens": 600 }
17
+ ```
18
+
19
+ Ranks the retrieval projection with the same `searchIndex` the CLI uses, so with no `kinds` the
20
+ `results` are what `ak-docs search <query> --json` prints for the same query and index — a test
21
+ compares the two rather than assuming it. Each result adds the entry's `title` and an `excerpt`,
22
+ the opening of the projected body. `kinds` filters after ranking, from a deeper ranking, so the
23
+ order never changes and a filtered call still returns up to `limit`. `explain` attaches the
24
+ matched terms and every score component, as on the CLI.
25
+
26
+ ## `knowledge.lookup`
27
+
28
+ ```json
29
+ { "id": "area:src/mcp", "depth": 1, "budgetTokens": 900 }
30
+ ```
31
+
32
+ Accepts an entity id, an ownership id, an alias or a `path`, through the same resolution
33
+ `handoff.resolve` uses. One response carries:
34
+
35
+ | Section | What it is |
36
+ | --- | --- |
37
+ | `entity` | id, kind, path, title, summary, aliases, symbols, tags, provenance, confidence, content hash, ownership, area, package, PageRank |
38
+ | `neighbours` | every entry within `depth` hops, grouped by relation kind (`covers`, `mentions`, `links-to`, `imports`, `contains`, …), each with direction, confidence, distance and the entry it was reached through |
39
+ | `documents` | the documents that `cover` the entity and those that `mention` it, most canonical first |
40
+ | `handoff` | exactly what `handoff.resolve` returns for the entity, `related` included |
41
+ | `diagnostics` | the open diagnostics of the latest reconciliation report that name the entity or point at its file, with the report hash — or `reportHash: null` when no workflow run exists |
42
+ | `evidence` | the entity's path and content hash, then those of the documents about it, each with an `excerpt` |
43
+
44
+ `contains` is not a projection edge — it is hierarchy — but an agent asking about an area wants its
45
+ modules listed, so the lookup synthesises it from `areaId` and `packageId`. Neighbours are
46
+ bounded per relation kind and visited in sorted order, so two lookups over the same projection
47
+ produce the same response. `depth` is at most 3.
48
+
49
+ `format: "text"` on either tool renders the same payload as prose through
50
+ `formatRetrievedDocuments`, for clients that prefer it.
51
+
52
+ ## Budgets
53
+
54
+ When `budgetTokens` is present, both tools and `handoff.resolve` trim through `compileBudget`:
55
+ each droppable section becomes a message, oldest first in the order it may be dropped, the payload
56
+ with every section removed becomes the last message, and `drop-oldest` with `keepRecent: 1` sheds
57
+ sections from the front until the rest fits. The declared order is
58
+
59
+ ```
60
+ evidenceExcerpts → related → neighbours → summaries
61
+ ```
62
+
63
+ and nothing else is ever dropped: the entity, the evidence paths and hashes, the handoff fields an
64
+ agent acts on and the open diagnostics survive every budget. A payload whose undroppable core
65
+ still exceeds the budget reports `fits: false` with every section dropped, rather than truncating
66
+ what it may not drop. A section a payload does not have — search has no `related`, a handoff has
67
+ no `neighbours` — is absent from the report, never reported dropped.
68
+
69
+ ```json
70
+ {
71
+ "budget": {
72
+ "budgetTokens": 900,
73
+ "tokens": { "total": 790, "budget": 900, "core": 684, "sections": { "evidenceExcerpts": 59, "related": 290, "neighbours": 229, "summaries": 106 } },
74
+ "fits": true,
75
+ "order": ["evidenceExcerpts", "related", "neighbours", "summaries"],
76
+ "kept": ["summaries"],
77
+ "dropped": ["evidenceExcerpts", "related", "neighbours"],
78
+ "tokenMethod": "approximate"
79
+ }
80
+ }
81
+ ```
82
+
83
+ Tokens are counted with `approximateCounter` — four characters per token plus two per message,
84
+ over the serialised sections — and reported as `tokenMethod: "approximate"`. `compileBudget` and
85
+ `approximateCounter` are mirrored in `src/budget/compile.ts` because `@agentskit/core` is an
86
+ optional peer and every query surface answers with no peer installed; a test runs the real
87
+ `compileBudget` over the same messages and asserts the two agree on every token count, every
88
+ dropped message and `fits`. On a handoff the droppable sections are `related` and the note that
89
+ repeats the target's summary; `budget` is an optional field of `AgentHandoffV1`, so a budgeted
90
+ handoff is still a valid handoff.
91
+
92
+ ## Every existing tool keeps working
93
+
94
+ `handoff.resolve`, `doc.search`, `doc.get`, `gate.status`, `retriever.query`, `memory.*`,
95
+ `registry.topology` and `docbridge.*` keep their names, arguments and payloads. `handoff.resolve`
96
+ gains an optional `budgetTokens`; `docbridge.diagnostics` gains an optional `format`. The new tools
97
+ are appended to the advertised list and to the configuration's `surfaces.mcp.tools` default, so a
98
+ configuration that names its tools explicitly is unchanged until it names them.
99
+
100
+ ## Canonical findings
101
+
102
+ ```
103
+ ak-docs check --json --format finding
104
+ docbridge.diagnostics { "format": "finding" }
105
+ ```
106
+
107
+ Both emit every reconciliation diagnostic as a `Finding` from `@agentskit/core/finding`, with
108
+ severities drawn from `SEVERITY_ORDER`, so Code Review, AKOS and dashboards read Doc Bridge with no
109
+ parser of their own. Internal severities map `error → high`, `warn → medium`, `info → low`,
110
+ `off → info`; nothing is `critical`, because a documentation finding never takes a system down.
111
+ `title` is the code as words, `detail` the message, `category` the status, `location` the first
112
+ evidence path and line, `ref` the code, and `metadata` carries the internal code, status,
113
+ severity, evidence and entity ids, so nothing is lost. `confidence` follows the status: an
114
+ observed relation is certain, a stale declaration less so, a coverage gap least of all. Findings
115
+ are ordered most severe first and by id within a severity.
116
+
117
+ This is a reporter, not a migration: `KnowledgeDiagnostic`, `RuleFinding` and
118
+ `DocumentationAuditFinding` keep their shapes, and the rule verdict still decides the exit code
119
+ of `check`. A test imports the real package and asserts assignability and the severity order.
120
+
121
+ ## The measured doctor
122
+
123
+ Three dimensions join the score and can lower the grade:
124
+
125
+ | Dimension | Measures | Points |
126
+ | --- | --- | --- |
127
+ | Reachability | the share of the snapshot's document entities present in the retrieval projection | 15 |
128
+ | Connectivity | the mean of: areas with at least one covering or mentioning document; documents with at least one edge into code | 15 |
129
+ | Benchmark | hit@3 over the golden suite at `retrieval.benchmark.suite` (`docs/bench/retrieval-suite-v1.json` by default) | 10 |
130
+
131
+ The existing dimensions — index present and fresh, agent docs, human guides, gates — make up the
132
+ other sixty. A repository with no golden suite reports the benchmark as `not-analyzed`, scores
133
+ nothing for it and says so in the issues; it is never silently omitted.
134
+
135
+ An A requires all three: reachability at 100 percent, connectivity at 80 percent or more, and a
136
+ measured hit@3 of 80 percent or more. A score of 90 that misses one of them is a B, and the text
137
+ report says which. Reachability must be complete because a document retrieval cannot find is a
138
+ document the product does not deliver; the other two have a floor rather than a ceiling.
139
+
140
+ On this repository every document is in the projection and the benchmark is at 88.3 percent, but
141
+ 20 of 39 areas have no document about them and 55 of 100 documents do not point at code —
142
+ connectivity 47 percent, grade B. That is the honest state. A test proves the other direction on
143
+ the same repository: one document out of the projection is enough to lose the A, and an index
144
+ with no projection at all — what the builder produced before the corpus projection — is at zero.
145
+
146
+ The doctor's `ok` is unchanged: it is still "no error-severity issue and the gates pass", so a
147
+ CI step that runs `ak-docs doctor --text` fails on a stale index, not on a B.
@@ -0,0 +1,229 @@
1
+ ---
2
+ title: Measured enrichment v1
3
+ description: What an enrichment run cost, what it invented, whether it improved retrieval at all, and the study measurements — mechanical task expectations, the assisted arm, tokens to first evidence — that decide the same questions for a controlled round.
4
+ ---
5
+
6
+ # Measured enrichment v1
7
+
8
+ An enrichment stage nobody measures is a stage nobody can defend.
9
+
10
+ [Enrichment overlay v1](./enrichment-overlay-v1.md) made agent proposals typed, validated and
11
+ bounded. It did not say whether any of it helped. The overlay reported what it accepted, which is
12
+ the one number an agent cannot fail: a curator that proposes a hundred things and has ninety
13
+ rejected looks exactly like one that proposes ten good ones. And the controlled study could not
14
+ answer the question either — its last round recorded zero semantic successes in both arms, because
15
+ a task whose only success criterion is a model's opinion produces no signal, and its third arm has
16
+ been reserved since the first suite without ever running.
17
+
18
+ This document is the measurement layer: the whole shape of an enrichment run, the retrieval delta
19
+ that can block it, and the study machinery that asks the same questions of a controlled round.
20
+
21
+ ## What the run reports
22
+
23
+ `EnrichmentStats` in `src/schemas/enrichment.ts` is written by `runEnrichment` and stored on the
24
+ overlay. It is deliberately outside the overlay's content hash — two runs over one unchanged
25
+ repository must agree on their decisions while disagreeing about how long they took.
26
+
27
+ | Field | Meaning |
28
+ | --- | --- |
29
+ | `byKind` | Per proposal kind: `proposed`, `accepted`, `pending`, `rejected`. Sorted by kind, so two runs produce the same bytes. |
30
+ | `rejectionReasons` | A histogram over the closed list of rejection reasons, sorted by reason. A run's rejections are comparable across runs rather than a log to read. |
31
+ | `inventedReferences` | Rejections that named something the repository does not contain. |
32
+ | `agentRuns`, `inputBytes`, `outputBytes` | What was actually sent and received. Batched packs, so a run is not a call count. |
33
+ | `cacheHits`, `cacheHitRate` | Hits, and hits over hits plus runs, rounded to six places. |
34
+ | `wallTimeMs` | Measured from a monotonic clock, not derived. `runEnrichment` takes a `clock` option so a test can pin it. |
35
+ | `expired` | Entries whose target content hash moved since they were accepted. |
36
+
37
+ `enrichmentCost(stats)` in `src/enrich/stats.ts` is the same numbers as an object for a reader who
38
+ only wants the bill; `ak-docs enrich --json` reports it under `cost`.
39
+
40
+ ### Invented references
41
+
42
+ `INVENTED_RELATION_REASONS` is `unknown-endpoint`, `unknown-entity`, `unknown-scope`,
43
+ `unknown-directory`, `unknown-diagnostic`. `inventedReferences` counts exactly those, and it is
44
+ reported next to the rejection total rather than folded into it.
45
+
46
+ The distinction is not cosmetic. A curator that classifies a document badly is wrong about a
47
+ judgement, and a validator rejecting it is the system working. A curator that proposes a relation
48
+ to a module the repository does not contain is making things up, and that number must trend to
49
+ zero or the agent is unusable. Folding the two together hides the second inside the first.
50
+
51
+ A schema failure or an unknown kind is a rejection and not an invention: `invalid-kind` is not in
52
+ the list, and a test asserts the counts stay apart.
53
+
54
+ ### Stability
55
+
56
+ `enrichmentStability(current, previous)` compares a run with the one before it and reports both
57
+ halves of the question at once:
58
+
59
+ - `overlayHashIdentical` — a deterministic agent over an unchanged repository must reach this. The
60
+ overlay hash covers decisions, not `acceptedAt` and not `stats`, so a second run that is answered
61
+ entirely from the cache produces the same hash as the first.
62
+ - `proposalIdShare` — for a live model, which will not reach an identical hash, the share of
63
+ proposal identifiers present in both runs. The share is over the **union** of the two runs'
64
+ identifiers, so a run that merely proposes fewer things does not score as more stable.
65
+
66
+ `sharedProposalIds`, `proposalIds` and `previousProposalIds` are reported next to the share, because
67
+ a share of 0.5 over two identifiers and over two hundred are different facts.
68
+
69
+ ## The retrieval delta
70
+
71
+ `measureOverlayRetrievalDelta` in `src/bench/overlay-delta.ts` answers the question the rest of the
72
+ statistics cannot: did the overlay make retrieval better?
73
+
74
+ It runs the golden suite twice over **one snapshot** — once with the accepted overlay projected,
75
+ once with `overlay: 'ignore'` — so the only difference between the two runs is the overlay. Not a
76
+ re-scan, not a different revision, not a different configuration. Neither half needs an index on
77
+ disk: both are projected from the snapshot the caller passes.
78
+
79
+ The rule is asymmetric on purpose. An overlay may leave retrieval unchanged and it may improve it,
80
+ but it must not lower `hitAt3` (`OVERLAY_BLOCKING_METRIC`): aliases, summaries and rank hints an
81
+ agent proposed exist to help an agent find things. A drop is reported as `regression: true` with
82
+ status `regressed`, and the caller exits non-zero. It is a finding about the agent, never a new
83
+ baseline.
84
+
85
+ The result carries both metric sets, a per-metric delta with `improved` and `worsened` (lower is
86
+ better for `meanContextBytes`, `meanApproxTokens` and `zeroResultRate`), and — the part that makes a
87
+ regression actionable — `lostCases` and `gainedCases`, the case ids that changed at hit@3.
88
+
89
+ ```bash
90
+ # The overlay on disk, against the configured golden suite.
91
+ ak-docs bench retrieval docs/bench/retrieval-suite-v1.json --overlay --text
92
+
93
+ # Or as part of the run that produced it.
94
+ ak-docs enrich --retrieval-delta --json
95
+ ```
96
+
97
+ Both exit 1 on a regression. Both report the delta in text and in JSON: `formatOverlayRetrievalDeltaText`
98
+ for a person, `retrievalDelta` (or `overlayDelta`) for a machine. The delta is opt-in because it runs
99
+ the suite twice, which is the right cost for an answer about whether the overlay helped and the wrong
100
+ cost for every routine run.
101
+
102
+ ## Study task expectations
103
+
104
+ The study's correction is the same idea one level up: state what retrieval is expected to return,
105
+ and check it deterministically.
106
+
107
+ A task in `docs/study/task-suite-v1.json` may now declare `expectedEntities`, `expectedDocuments`
108
+ and `retrievalQueries`. The references are **opaque** — `primary-entrypoint`, not `docs/alpha.md` —
109
+ because the task suite is publication-bound and a repository path in it is a privacy failure, not a
110
+ convenience. A query with nothing expected is refused by the schema: it would check nothing.
111
+
112
+ The resolution lives in a second artifact, `StudyExpectationsV1` in `src/study/expectations.ts`,
113
+ which declares `scope: 'local'` and binds to the suite by `taskSuiteHash`. It maps each reference to
114
+ the concrete entity ids or document paths it stands for in one repository on the operator's disk.
115
+ It is never published; putting it under `docs/study/` would fail the privacy gate, which is the
116
+ intended outcome rather than a bug. References resolved against a suite that has moved on are
117
+ refused outright.
118
+
119
+ `studyRetrievalSuite` turns the pair into an ordinary Open Eval Format suite — one case per task
120
+ query, with the resolved targets as `expectedTargets` and the task, repository and category carried
121
+ in metadata — so the same command, the same ranking and the same metrics that gate this
122
+ repository's retrieval answer the study's mechanical questions. `checkStudyExpectations` runs it:
123
+
124
+ ```bash
125
+ ak-docs study expectations docs/study/task-suite-v1.json \
126
+ --expectations ./local-study-expectations.json --index .doc-bridge/index.json --text
127
+ ```
128
+
129
+ `ok` requires that every reference resolved **and** every case hit. Two failures are reported rather
130
+ than smoothed over: a task that declares no expectations is listed under `withoutExpectations` as
131
+ unchecked, never counted as a pass, and an unresolved reference fails the check, because an
132
+ expectation nobody resolved is an expectation nobody tested — which is exactly the failure this
133
+ replaces. The command exits 1 when the check does not pass.
134
+
135
+ `validateStudyTaskSuite(suite, { requireExpectations: true })` names the tasks that cannot be checked
136
+ mechanically. It is off by default: a suite written before expectations existed is still a valid
137
+ suite.
138
+
139
+ ### Where the model adjudicator stops
140
+
141
+ A rubric item may now be prose, or prose with the mechanical check that decides it
142
+ (`acceptance-checks`, `evidence-coverage`, `retrieval-expectations`). `mechanicalRubricItems` and
143
+ `modelRubricItems` split the rubric on that field, and `adjudicatorRubric(task)` is what the
144
+ adjudicator input carries: `rubric` holds only the items no checker can settle, and the settled ones
145
+ travel next to them as `mechanical` — verdicts, for context, not for review. The instruction says so
146
+ in the same words.
147
+
148
+ Handing a model an item the runner already measured invites it to disagree with a measurement, which
149
+ is how a study ends up with an opinion where it had a number.
150
+
151
+ ## The assisted arm
152
+
153
+ `registry-assisted` has been reserved since the first suite and has never executed. It now reports
154
+ its own readiness, and `assistedArmReadiness(plan, providers, suite)` is the only thing that decides
155
+ whether it runs:
156
+
157
+ | Status | When | Effect |
158
+ | --- | --- | --- |
159
+ | `unavailable` | No assisted scenario in the plan; the scenario names no agent identity and version; no provider CLI for the scenario and a model the suite uses | Its executions are recorded as unavailable observations |
160
+ | `ready` with `undeclared` | The scenario declares no `promptVersion`, or no `agentBudget` | The arm runs; the run says what it could not name |
161
+ | `ready` | Everything declared | The arm runs |
162
+
163
+ An unavailable arm is **recorded, not skipped**: a scenario absent from a ledger is
164
+ indistinguishable from one that was never planned, and comparing the arms it planned is the study's
165
+ whole purpose. Each such execution lands in the ledger with `execution.status: 'unavailable'`,
166
+ `errorCode: 'registry-unavailable'`, and an automated adjudication of `blocked` carrying the reason.
167
+ A missing Registry is a fact about the environment, not a reason to lose the other two arms —
168
+ `assertRunInputs` skips provider validation for those executions so the rest of the run proceeds.
169
+
170
+ A missing declaration is the other way round: losing the third arm over an undeclared prompt version
171
+ would be worse than running it without one, so the arm runs and `undeclared` says what a reader
172
+ cannot reconstruct from the ledger — the prompt it used, or the agent's cost apart from the model's.
173
+ The run summary prints the status, the reason and the undeclared fields.
174
+
175
+ A run plan may declare `promptVersion` and `agentBudget` only on `registry-assisted`: the other two
176
+ arms have no agent to budget.
177
+
178
+ ## Tokens to first evidence
179
+
180
+ The parent PRD's second claim is that an agent reaches correct grounded evidence for fewer tokens
181
+ than by reading the repository. Total tokens at the end of a task does not test that claim: a run
182
+ that wandered for ten thousand tokens and then found the answer looks the same as one that landed on
183
+ it immediately.
184
+
185
+ `tokensToFirstEvidence` is a canonical provider measurement — tokens consumed before correct
186
+ grounded evidence was in hand — and the metrics report its p95 per scenario as
187
+ `tokensToFirstEvidenceP95`, rounded up to whole tokens. It is in the improvement list, so a round
188
+ that reaches evidence sooner reads as an improvement, and absent or partial coverage is reported as
189
+ `tokensToFirstEvidence` or `tokensToFirstEvidence-partial` under `missingMetrics` rather than as a
190
+ zero.
191
+
192
+ The assisted arm's enrichment agent is costed apart from the model: `registryAgentInputTokens`,
193
+ `registryAgentOutputTokens`, `registryAgentCostUsd` and `registryAgentRuns`. `registryAgentCostUsd`
194
+ is added into `totalCostUsd`, so the arm cannot look cheap by charging its work to a line nobody
195
+ adds up, and `missingMetrics` names it when an assisted observation reports no agent cost at all.
196
+ The scenario line in `--text` prints the p95 next to the agent cost and its run count.
197
+
198
+ ## Invariants
199
+
200
+ - `stats` is outside the overlay's content hash. Cost and timing may differ between two runs that
201
+ decided identically.
202
+ - `inventedReferences` counts only `INVENTED_RELATION_REASONS`, and is reported separately from the
203
+ rejection total.
204
+ - `proposalIdShare` is over the union of both runs' identifiers.
205
+ - The retrieval delta builds both indexes from one snapshot. An overlay on disk cannot leak into the
206
+ baseline half.
207
+ - An overlay that lowers hit@3 is a regression with a non-zero exit, in every mode.
208
+ - A study task's expectations are opaque references. A path or a URL in a publication-bound artifact
209
+ is a privacy failure, and the privacy gate is what says so.
210
+ - An unresolved reference fails the check; a task without expectations is reported unchecked and is
211
+ never a pass.
212
+ - A rubric item with a mechanical check never reaches the model adjudicator.
213
+ - An unavailable assisted arm is recorded as unavailable observations, and never fails the study.
214
+
215
+ ## Deviation: the committed study suite
216
+
217
+ The mechanism above is delivered and tested; the committed 24-task suite, run plan and ledgers are
218
+ **not** rewritten to use it.
219
+
220
+ Two reasons, both about not making the artifacts worse. The suite's content hash is bound to
221
+ published artifacts — the run plan's `taskSuiteHash`, the observation ledgers, the verification
222
+ binding — and changing it invalidates every one of them. And the concrete targets an expectations
223
+ file would resolve belong to the six study repositories, which are not present in this repository,
224
+ so the references could only be guessed.
225
+
226
+ An operator adding expectations to a round therefore does three things: add `expectedEntities`,
227
+ `expectedDocuments` and `retrievalQueries` to the tasks, re-seal the suite, and write the local
228
+ expectations file that resolves the references against their checkouts. `ak-docs study expectations`
229
+ then reports which tasks can be answered mechanically and which cannot.