Repository navigation
Epic: risk_documentation becomes a per-unit coverage ratio (Phase 4 score contract; child of #2812) #2908
Description
Child of #2812 (Phase 4 · score contracts). First formula to get a score contract; the pattern here is meant to be reused for risk_api_exposure, risk_tech_debt and func_complexity_gini, the other score-layer rows still carrying open-defect cells.
Labels: epic metrics core-engine roadmap
Why this epic exists (one paragraph)
_calc_documentation does not measure documentation. On the rosetta corpus every term but one is flat: no function has impact above 50 (the "opaque execution" term is dead), every file is under the 50-line evidence floor (the denominator is a constant 70), and the multipliers do not move the observed values. What remains is a sigmoid over 2 × api, so one adjusted-api hit per file is worth about 13 points on a 0–100 scale. The adjusted api count is assembled from two overlapping sources (the language's api rule plus the orphan-census credit, minus a tokenizer-dependent overlap), and every way that assembly fails lands in a different score cluster: 79 where a name is counted twice (m4, makefile, scheme, cobol), 55 where the rule sees one token and the census credits three (css, agc_assembly, yacc), 18 where the census is unanswerable or broken (dockerfile, html, yaml, haskell), 0 where the rule matches nothing (jcl, sqlite, objective-c main). On real code the same equation flips regime: a 500-line file with the same three undocumented public functions scores about 15, because the denominator is now the file length. That is the −0.83 length leak the bias report already flags, seen from the other side. Four of the eleven open-defect cells on the scoreboard and the corpus's single confirmed length leak are this one formula.
The goal restated: risk_documentation is the share of a file's callable surface a reader cannot recover from documentation. A ratio over units, not a density over lines. Identical intent scores identically by construction, not by tuning.
The contract
risk_documentation— of the units extracted from a file (functions, methods, paragraphs, steps), the weight-share that is undocumented. A unit is public when its name is in the file's public name set. A unit is documented when adoc-rule match is anchored to its header window. Public units weigh double; a unit's weight rises by one perreflection_metaprogramminghit inside it. A folder-level documentation umbrella shields the whole file multiplicatively. A file with no extracted units has no value (n/a), not zero.
for each extracted unit u:
weight(u) = (2 if public(u) else 1) + reflection_hits_inside(u)
exposed(u) = weight(u) if not documented(u) else 0
risk_documentation = 100 × Σ exposed(u) / Σ weight(u) × (1 − umbrella_shield × doc_umbrella)
= n/a when Σ weight(u) = 0
Definitions that carry the contract:
- public name set = names the
apirule matched on the unit's own header ∪ names in an export list (_visibility_export/_visibility_export_list, the unreferenced_by_name reads 0.25/file for haskell and scheme: a module export list and a type signature are declarations, not references #2823 machinery) ∪ units left uncalled in a file that something imports (the Contextual Baseline Fix's orphans). A union of names: a unit cannot be public twice, and a tokenizer miss costs one unit rather than doubling the file. This is the per-unit form of raw_arch_api bands a per-file declaration count against a per-function median — one granularity mismatch, three red columns #2771's complaint (a per-file declaration count banded against a per-function median). - documented = a
doc-rule match whose span ends inside the unit's header window: the k lines immediately beforestart_line, or the leading lines of the body (docstring position). Not the slicer'sdocstringfield, which is a per-language "preceding comment" heuristic: on the corpus it captures python's planted# HACK:comment as a docstring, reads 13 of 13 for jcl, and 0 for fortran, haskell and scheme. - reflection inside the unit is engine PR 2: per-file dynamism count replaces the flat irc term (child of #2716) #2719's dynamism kept where it belongs: runtime-decided behaviour raises the weight of that unit instead of adding a hit to a file total.
- umbrella =
doc_umbrellaas today (GuideStar folder coverage, README-upgraded). The only file-level defence that survives.
What leaves the equation, with the reason:
| term | why it goes |
|---|---|
doc_loc × 0.33 |
a comment-line count is not coverage, and it is one of the two length terms |
ownership × 0.5 |
who maintains a file is a different question, already carried by silo/authorship views |
opaque_execution (impact > 50 ∧ no docstring) |
dead on small functions; its intent (load-bearing undocumented code) is what public weighting expresses |
/ (_mass_loc(loc) + 20) and the sigmoid |
the length regime; the ratio is already 0–100 |
popularity and silo multipliers, path modifier mp |
blast radius sits next to the score in the same report row; a coverage ratio does not change because the file lives in a config folder |
doc_weight, ownership_weight, doc_loc_weight, loc_smoothing, threshold_base, sigmoid_slope |
six knobs become two: public_weight (2.0), umbrella_shield (0.5) |
Weights are counts, not lengths. Per-function impact contains 0.05 × effective_loc; weighting by it re-imports length through the side door. Impact stays the hitlist's ranking key.
Decisions to approve before Phase 2 starts
- D1 The score is a ratio over extracted units; no per-LOC term, no sigmoid, no small-file floor.
- D2
public(u)is the name-set union above. This is the same setrisk_api_exposureshould read; that formula's own contract is a separate epic but shares Phases 1–2 here. - D3
documented(u)is the header-anchoreddocmatch, notdocstring.docstringis left as is (it feeds the brief's text), not deleted. - D4 Ownership, doc_loc, opaque_execution, popularity/silo multipliers and
mpleave the equation.path-and-extension-modifiersin the ledger dropsrisk_documentationfrom its signal list when this lands. - D5 No damping knob for one-unit files (0 or 100). Roll-ups are already mass-weighted. Revisit only if Phase 5 shows hitlist noise.
- D6 Engine keeps emitting a float;
n/ais inferred by the reporting layer from unit count 0, the same convention the corpus tooling already uses for rule absence (noNonein the risk vector).
Expected values (acceptance numbers)
| file | today | after |
|---|---|---|
| rosetta a/b/c, every language | 17.9 → 79.2 depending on api assembly | 100 (3 public units, 0 documented) |
rosetta main, entry documented |
33.4 | 75 where the api rule sees entry, 86 where it does not (an api contract cell, surfaced on purpose) |
| css, yaml, html, sqlite, markdown | 0 → 55 | n/a |
| 500-line file, 10 public units, 2 documented | ≈ 15 | 80 |
| 20-line file, 3 public units, 0 documented | 40.6 | 100 |
length-leak row (bias_report.md) |
rho −0.83, leak | rho 0 by construction |
Phases
-
0 · Design freeze
gitgalaxy—docs/risk_documentation_contract.md(the contract sentence, the equation, the leave-list table, D1–D6), and the per-unit decomposition that produced the cluster table above committed astests/tools/audit_documentation_inputs.py(scan → per-filearch_api, credit,doc_loc, score; reproduces every corpus score from the inputs). Approve D1–D6 on this issue.
done when the contract doc is merged asdraftand the acceptance table above is agreed. -
1 · The name set is right before anything reads it
gitgalaxy + keyword-rosetta— prerequisites, all already filed or owed:- api_declared_orphans tokenizes api lines with
\b\w+\b, so a hyphenated or qualified name is never recognised as already-declared (#2731's double count returns for cobol and scheme) #2827 —api_declared_orphanstokenizer (\b\w+\bcannot seePROBE-GLOBALS,probe-globals); cobol + scheme, 3 cells - m4/makefile corpus re-plant against Add visibility-export rules for m4 and makefile (#2872) #2902's export rules (engine half merged; corpus half owed, keyword-rosetta#53 lineage); 4 cells
- Contextual Baseline Fix reads a weaker dependency resolver than every column it is audited by (haskell) #2871 — haskell module names invisible to the popularity tally, so the credit never fires and orphans stay; 1 doc cell + the tech-debt outlier
- c api rule counts top-level variable declarations as public surface (a.c reads api 5 for 3 functions; +25.0% sits at the band edge) #2907 — c's
apirule counts top-level variable declarations (int shared_region =indata/c/a.creads api 5 for 3 functions);raw_arch_api3.75 vs median 3 sits at exactly +25.0%, in band by zero margin, unledgered
done whenarch_apireads 3 in every a/b/c file that has an api rule, and therisk_api_exposure/raw_arch_api/risk_documentationopen cells attributable to these are 0.
- api_declared_orphans tokenizes api lines with
-
2 · Per-unit attributes in the detector
gitgalaxy(T2) — three attributes on every extracted unit:is_public(name-set union, D2),is_documented(header-windowdocmatch, D3),reflection_hits(the unit's ownreflection_metaprogrammingcount, already sliced per function). Recorded as threefunction_datacolumns and in the audit JSON. Counts do not move: golden masters change by new columns only.- detector:
_public_name_set(file)from api header matches +_export_declaration_offsets+ healed orphans;_doc_anchored(unit)over the comment stream (thedocrule reads comments, per ownership has no stated contract: solidity counts the SPDX license line on every file, five languages count copyright notices and license prose, three count the phrase 'created by' in sentences, and five doc rules still double-count the author tag #2882's stream convention) - contract tests in
tests/core_engine/test_detector.py: one per declaration family (docstring-position, preceding-block, export-list, positional-invocation languages) keyword-rosetta:verify_language.pyreadsfunction_data; manifests gainunits_public/units_documentedper file; therule-contract-auditskill run once across 46 languages for the two attributes
done when 46/46 languages read public = 3, documented = 0 in a/b/c; main reads documented = 1 (entry) and public 3 or 4 with the split ledgered against theapicontract; no unexplained cell.
- detector:
-
3 · The equation
gitgalaxy(T2, labelrosetta:rebless-owed) — replace_calc_documentationwith the ratio; delete the six tuning keys inanalysis_lens.py, addpublic_weightandumbrella_shield; call site insignal_processor.pypasses units + umbrella only.- tests:
tests/core_engine/test_signal_processor.py(new pins),test_uef_length_invariance.pyandtests/tools/audit_length_invariance.py(documentation is now exactly length-invariant: assert equality, not tolerance),tests/tools/audit_risk_equations.py(no tier/fid term left to audit for this formula) - golden masters: both legs re-blessed via
tests/tools/crucible_check.py --update --yes; every file's documentation score moves, so the diff is scoped by the decomposition tool before blessing - prose that prints the equation:
llm_recorder.py("12. Documentation Risk Exposure …"),docs/wiki/08-03-transforming-regex-counts.md(the dynamism row),docs/gitgalaxy_architecture_brief.mdself-scan regen
done when both golden legs are blessed with a scoped diff, the length audit reads rho 0, and the corpus reads the acceptance table.
- tests:
-
4 · Corpus re-bless and scoreboard
keyword-rosetta— manifests to the acceptance values; ledger:path-and-extension-modifiersdropsrisk_documentation,m4-makefile-api-scores-above-stratumand theapi-declared-orphans-blind-to-non-word-namesdocumentation half retire on Phase 1's verifying scan;bias_report.py:risk_dependencies()learns the per-unit inputs so a documentation cell attributes to a unit,reads_constantflips to False (the formula stops readingfid, so the metric is no longer banded within a strictness stratum, which today hides yacc at +28% against the global median), the leak table row becomes the proof.
done whenrisk_documentationis 46/46 in band (n/a where declared), 0 open, and the report bands it against the global median. -
5 · Real-code validation
gitgalaxy— before/after on the language-crucible and on the two README example repos (curl, cics-genapp): score distribution, top-20 documentation hitlist diff, count of one-unit files at 100 and whether any reaches a hitlist. Decide D5 on evidence.docs/incidence_report.mdin keyword-rosetta gains the documentation row.
done when the before/after comment is on this epic and D5 is confirmed or a damping design is filed as its own issue. -
6 · Closure — the contract row goes
stated(score contracts get their own section insignal_contracts.py/docs/signal_contracts.md, the first entry);docs/contract_roadmap.md§4 Phase 4 marks this formula done; README "Accuracy, measured" copy refreshed with the live numbers (the paragraph still carries 2026-09-06's 90% / 2.7%;docs/validation.mdcarries 75% / 3 of 33).
done when open-defect cells attributable torisk_documentation= 0, the sheet row isstated, and the next score contract (risk_api_exposure) can copy this epic's phase list.
Sequencing
0 design freeze ──► 1 name set (4 items, independent of each other)
│
▼
2 per-unit attributes (counts unchanged)
│
▼
3 equation + golden bless ──► 4 corpus re-bless ──► 5 real-code check ──► 6 close
Phase 1 and Phase 2 can run in parallel; Phase 3 must not start until both are merged, because the ratio reads the name set and a wrong set would be blessed into the golden masters. One layer per engine PR (rule | slicer | scoring), as #2812 rule 4.
Non-goals
risk_api_exposure's own formula (its contract is the next epic; it inherits Phases 1–2 unchanged).- Changing
impact,docstringextraction, or the doc umbrella's computation. - Any damping for small files unless Phase 5 shows the need (D5).
- Re-tuning any other
_calc_*; the commensurability audit tool in Epic: contracts at every layer — stream, count, score (the rosetta roadmap; child of #2560) #2812 Phase 4 is a separate deliverable this epic will feed one worked example into.
Issue disposition
| issue | action |
|---|---|
| #2827, #2871 | Phase 1 prerequisites; keep, unchanged |
| #2872 | closed (engine half #2902); the corpus re-plant is Phase 1 item 2 |
#2771 (raw_arch_api granularity) |
answered by D2's per-unit name set; close when Phase 2 lands |
#2719 (dynamism replaces flat irc) |
kept, moved per unit (D1); no reversal |
| #2655 (evidence-mass floor) | the floor stays for the other density formulas; this formula stops reading it |
bias report length-leak row for risk_documentation |
resolved by construction in Phase 3; the row stays as the proof |
keyword-rosetta risk-documentation-zero-evidence-guard (retired) |
the guard's role (no surface → 0) becomes n/a (D6); note on the entry, no revival |
Evidence behind the "why"
Sensitivity of today's equation to the adjusted api count per file (all other inputs at the corpus values, doc_loc = 2):
| adjusted api / file | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
risk_documentation |
0 | 17.9 | 27.9 | 40.6 | 54.8 | 68.2 | 79.2 | 86 |
Where the corpus lands and why (per-file scan of 20 languages against engine main, 2026-09-09; every score reproduced from the formula):
| adjusted api / file | languages (a/b/c) | mechanism |
|---|---|---|
| 6 | makefile, m4, scheme, cobol | raw 3 + orphan credit 3: overlap check never recognises the names (#2827, #2872) |
| 5 | c (a.c only) | globals plants match the api rule as top-level declarations |
| 4 | css, agc_assembly, yacc | raw 1 + credit 3; yacc adds 4 reflection hits |
| 3 | the mainstream pack; objective-c via credit alone (raw 0) | intended |
| 1 | dockerfile, html, yaml (census n/a), haskell (#2871) | raw 1, no credit |
| 0 | jcl, sqlite, objective-c main | zero-evidence guard |
Activity
squid-protocol commentedon Sep 9, 2026
claiming Phase 0 — Fable 5.1 — branch
design/risk-documentation-contractstatus Phase 0: done pending CI — PR #2910 (
docs/risk_documentation_contract.mdasdraft+tests/tools/audit_documentation_inputs.py). 46/46 corpus languages, 185 files, every recordedrisk_documentationreproduced from its inputs at engine 81b895c.Filed from the decomposition:
globalsplants as public surface (a.c api 5 for 3 functions; +25.0% exactly at the band edge). Added as Phase 1 item 4.meta['popularity']is never set beforecalculate_risk_vector, so the popularity terms in documentation, verification, api_exposure and the threat vector are dead. D4's removal of the multiplier is therefore score-neutral today. Not blocking.D1–D6 are on the contract doc for review. Phase 1 next (#2907 first: smallest, unblocks c's cell).
squid-protocol commentedon Sep 9, 2026
status Phase 1 / #2907: engine PR #2911 open (
rosetta:rebless-owed), CI running. rule_probe crucible 1141 → 1040, rosetta 15 → 13, nothing gained; every lost match read. Corpus effect: c a.c api 5 → 3 only. Corpus re-bless ofdata/copens after #2911 merges. Remaining Phase 1: #2827, #2871, m4/makefile re-plant.risk_documentation score contract (draft) + per-file input decomposit…
c api: a file-scope variable is globals' hit, not public surface (#2907…
squid-protocol commentedon Sep 9, 2026
status Phase 1: #2907 merged (#2911); corpus re-bless keyword-rosetta#110 open and green. #2827 → engine PR #2914 open (
rosetta:rebless-owed): cobol + scheme a/b/c api_orphan_credit 3 → 0, nothing else moves; golden masters blessed (14 lua/perl/zig files on the four api-fed keys). Corpus branchrebless/2827-declared-orphans(both manifests + ledger retirement) prepared, opens after #2914 merges. Remaining Phase 1: #2871 (haskell), m4/makefile re-plant.squid-protocol commentedon Sep 9, 2026
status Phase 1: all four items now have PRs.
\b\w+\b, so a hyphenated or qualified name is never recognised as already-declared (#2731's double count returns for cobol and scheme) #2827 → api_declared_orphans: test the orphan's real name, not \b\w+\b tokens (#2827) #2914 (CI running); corpus branchrebless/2827-declared-orphansready, opens after merge.rebless/2871-haskell-ledgerready (ledger retirement only), opens after merge.m4-makefile-api-scores-above-stratumretired.When #2914/#2915 and the three corpus PRs land, the scoreboard's 11 open-defect cells drop to 1 (makefile func_complexity_gini, its own ledger entry, no issue yet) and Phase 1's done-when holds: arch_api reads 3 in every a/b/c that has an api rule. Phase 2 (per-unit is_public / is_documented / reflection_hits) is next and needs D1–D6 approved.
squid-protocol commentedon Sep 9, 2026
Efficiency assessment of this plan (2026-09-09, after Phase 0 + Phase 1)
Written from the session that ran Phases 0 and 1, so the numbers are what it actually cost, not a guess. The plan's phases are right. The way they were run was the expensive way, and the repo already owns the cheap way for rule contracts (
.claude/skills/rule-contract-audit,.claude/agents/contract-audit-scout.md,tests/tools/bless_scope.py). None of it was used here because none of it names the score layer. The fix is to make the score-layer twin of that kit before Phase 2, not after, because this will not be the last formula (risk_api_exposure,risk_tech_debt,func_complexity_giniare queued on this same epic's pattern).Where the tokens went (measured on Phases 0–1)
golden_diff+ bucketing) was written 2× althoughbless_scope.pyalready does it-C), a background job launched from the wrong cwd (the shell's cwd had drifted), a stale corpus worktree that produced a phantomhigh_risk_executionmismatch, acdinside aghcommand that moved the shell for every later callworktree_envscript that creates/refreshes and prints the exports; absolute paths alwaysgh pr checkspolled by hand a dozen times across four PRs--watchin the background per PR, then nothing until it notifiesThe irreducible orchestrator work was small and is the only part worth the strong model: the contract sentence and D1–D6; the read-every-lost-line pass on a rule change (the first #2907 candidate lost 21 Doom K&R definitions — a scout would have reported "21 lost lines start with a bare return type", the decision was one line); the per-cell verdicts in the ledger; and the go/no-go on each bless scope. Everything else was retrieval, measurement and formatting.
What to build before Phase 2 (each is an issue: gitgalaxy #2916, keyword-rosetta#113)
Engine (
tests/tools/):audit_score_inputs.py <risk_metric>— generaliseaudit_documentation_inputs.py: an adapter table per_calc_*(DB columns → call arguments, theEQUATION_CASESshapeaudit_length_invariance.pyalready uses), reproduce every recordedrisk_<metric>from its recorded inputs, print the cluster table by dominant input and the sensitivity table, exit 1 on residual. Phase 0 of any score contract becomes one command and one digest. Also the tool that says, for a formula nobody has looked at, which input actually drives it.rule_probe.py --diff-lines before.json after.json— the lost/gained matched-lines listing (with 2 lines of context for multi-line matches). The read-every-lost-line pass is the orchestrator's; the listing is not.contract_pr_check.py— runs the gauntlet legs in parallel from one worktree (extraction tests for the touched languages,rosetta_auditagainst the corpus worktree at origin/main,crucible_check,bless_scopeon the result,audit_check) and prints the PR-body block. One invocation, one digest, no retyping.worktree_env.sh <name> [--corpus]—git fetch, create or movegitgalaxy-worktrees/<name>(andkeyword-rosetta-worktrees/<name>) toorigin/main, printexport PYTHONPATH= GITGALAXY_PATH= GALAXYSCOPE_BIN= LANGUAGE_CRUCIBLE_PATH= KEYWORD_ROSETTA_PATH=for copy-paste. Refuses to run inside the primary checkout..claude/agents/contract-audit-scout.md: the environment section names/srv/storage_16tb/projects/gitgalaxy/v6/.venv, which is not this machine's venv (the working one is~/nyx_projects/gitgalaxy/.venv, full precision at.crucible_venvs/full_precision). A scout dispatched today runs the wrong engine or fails on import. Fix the paths, addaudit_score_inputsandcontract_pr_checkto its job list..claude/skills/score-contract-audit/SKILL.md— the score-layer twin ofrule-contract-audit: same tier table (orchestrator / scout / build / adversary), same "never delegate" list, phases = this epic's phases with the exact commands, the D-decision template, the acceptance-table template, anddocs/_score_contract_template.mdwith the sectionsrisk_documentation_contract.mduses (sentence · equation · what today's formula measures · leave-list with reasons · decisions · acceptance · consumers · phase map).Corpus (
keyword-rosetta/tools/):rebless.py <lang> --engine <worktree> --note "<dated sentence>" [--retire <ledger-id> --resolved-by gitgalaxy#N --verdict "..."]— scan with the named engine, diff observed vs manifest, apply only the cells that moved (printing them), append the note, retire the ledger entry, preserve the formatting the files actually use (manifestindent=2, ensure_ascii=False; ledgerensure_ascii=True), re-runverify_language. The 4× hand-rolled python, once.screen_plant.py --engine <worktree>— it hard-codes the primary checkout today (the skill already warns about this).open_rebless_pr.sh <N>(or abias-history.ymlstep): when engine main carriesCloses #Nand a pushed branchrebless/<N>-*exists, open the corpus PR with the standard body. The engine-first merge order is inherent; the remembering is not. Three corpus branches sat pushed-but-unopened this session waiting on human merges.ledger_show.py <id> [--short]— id, signal, languages, disposition, status, the first and last 300 chars of the verdict. Reading a whole entry to check one field cost ~3 KB each time.Per-phase checklists for THIS epic (copy into the phase comment when claiming it)
Phase 2 · per-unit attributes (
gitgalaxy)worktree_env.sh fix-2908-p2; claim comment on this issue.audit_score_inputs.py risk_documentationdigest at engine main — the baseline every later step is compared to.is_public(name-set union: api header match ∪ export-list names ∪ healed orphans),is_documented(header-windowdocmatch; k lines beforestart_lineor leading body lines),reflection_hits. Name the header window's k and the docstring-position rule per lexical family before any code.function_datacolumns + audit JSON; contract tests per declaration family (docstring-position · preceding-block · export-list · positional-invocation).rosetta_audit46/46 (counts must not move — this phase adds columns only);crucible_check(expect new columns only;bless_scopeleaf-key table must show no signal key).units_public / units_documentedfor a/b/c/main → orchestrator decides the 3-vs-4 dispatcher split per language against the api contract and ledgers it.verify_language.pyreads the two new keys; manifests gain them (rebless.py --add-keys); one ledger entry for the split.contract_pr_check.py→ PR body; labelrosetta:rebless-owed; push the corpus branch before the engine PR merges.Phase 3 · the equation (
gitgalaxy)_calc_documentation; delete six tuning keys, add two; updatetest_signal_processor.py,test_uef_length_invariance.py,audit_length_invariance.py(equality, not tolerance),audit_risk_equations.py; the brief text inllm_recorder.py; wiki 08-03.audit_score_inputs.py risk_documentationat the new formula — every corpus file must read the acceptance table (100 / 75-or-86 / n/a);bless_scopeon the golden masters — leaf-key table must beDocumentation Exposure+ its aggregates only; length-leak row rho 0.contract_pr_check.py→ PR body;rosetta:rebless-owed.Phase 4 · corpus re-bless (
keyword-rosetta)rebless.pyfor all 46 (one loop);path-and-extension-modifiersdropsrisk_documentation;bias_report.py'srisk_dependencies()learns the per-unit inputs andreads_constantflips;na_check/decoy_check/ledger_orphan_checkclean;open_rebless_pr.sh.Phase 5 · real-code check — ▸ scout: before/after top-20 documentation hitlist on curl + cics-genapp + the crucible, count of one-unit files at 100; orchestrator decides D5 from the digest.
Phase 6 · closure — sheet row
stated; roadmap tick; README +validation.mdnumbers refreshed (both still carry September-6 figures).Rules of thumb that fell out of this session
--summary.worktree_env.shprints them; nothing else should.decoy_checkafter any api-rule change; a rule narrowing can drop a comment decoy below the floor (it did, for c).api_declared_orphans: test the orphan's real name, not \b\w+\b tokens (…
Contextual Baseline Fix: case-fold the resolver for case-insensitive-…
feat(tools): the score-contract tooling batch — closes #2916 (#2931)
5 remaining items
Per-unit is_public / is_documented on function_data; reflection stays…
squid-protocol commentedon Sep 10, 2026
claiming Phase 3 — Fable 5 — branch
feat/2908-p3-equationsquid-protocol commentedon Sep 10, 2026
status Phase 3: done pending CI — engine PR #2938 (
rosetta:rebless-owed, 2 commits).The equation is the contract doc §1 verbatim:
_calc_documentation(functions, doc_umbrella), weight = (public_weight 2.0 if public else 1) + the unit's own reflection hits, exposed if not documented, × (1 − umbrella_shield 0.5 × doc_umbrella); 0.0 emitted at unit count 0 (D6). Six tuning keys → two; loc/doc_loc/fid/ownership/opaque_execution/popularity/silo/mp all left per D4.One decision surfaced by measurement (folded into the formula + contract doc §5): the unit population excludes synthetic slices (#2691/#2792). sqlite's
CREATE_Statementbuckets otherwise scored a/b/c at 100 where the contract declares n/a — and the score could not reproduce fromfunction_data, which excludes those buckets. Corollary from the same scan: the corpus n/a class is html, markdown, sqlite (+ javascript's package.json), not Phase 2's six — dockerfile, jcl and yaml DO extract units (the grammar-recognised keyword constructs #2728 kept) and read 100 in a/b/c.Done-when accounting:
audit_score_inputs.py documentation: 185 files, 0 residuals at the new formula; a/b/c read 100 in all 129 unit-bearing files; mains read the acceptance shapes (16×75, 18×85.71, plus the ledgered splits: 2×57.14, 71.43 scheme, 87.5 css, 60, 50 — all api-contract cells for therisk_api_exposureepic).audit_length_invariance.pynow checks documentation over the WHOLE sweep (equality, not tolerance) — invariant by construction; the bias-report rho −0.83 row flips to the proof in Phase 4.audit_risk_equations.py: confirmed tier-blind (moved to NOT_TIER_PARAMETERIZED).bless_scope: 2203 diffs, 0 topological, ALL under Documentation Exposure + its declared aggregates. Crucible avg_documentation 7.52 → 52.36 (the re-price; Phase 5 examines the distribution and decides D5).contract_pr_check --score: ALL GREEN (rosetta_audit 46/46 zero count regressions; audit_check battery clear).Phase 4 (corpus re-bless + bias report) opens after #2938 merges: manifests to the measured values above,
path-and-extension-modifiersdrops risk_documentation,reads_constantflips, ledger retirements per the epic.risk_documentation becomes the per-unit coverage ratio (#2908 Phase 3) (
squid-protocol commentedon Sep 10, 2026
claiming Phase 4 — Fable 5 — corpus branch
rebless/2908-p3-documentation(keyword-rosetta)squid-protocol commentedon Sep 10, 2026
status Phase 4: done pending CI — corpus PR keyword-rosetta#120.
No rebless: Phase 3 moved no counts, and manifests carry counts, not risk values — the corpus half of this phase turned out to be report machinery, not manifest edits. What landed:
reads_constantflipped to False on its own at the first post-merge regen (the formula no longer takesfid), sorisk_documentationis banded against the global median — this also un-hides the strictness-stratum banding artifact the epic called out (yacc is now measured against the global median like everyone else).risk_dependencies()AST-derives the per-unit edges (functions_found,units_public,units_documented,units_public_documented, per-unit reflection); the totals are cached as an ungated attribution group, so a documentation cell attributes to a unit.functions_found's own accounting. They had been coasting onpath-and-extension-modifiers, which now dropsrisk_documentationper D4.Done-when accounting: 43/43 comparable cells in band, 3 n/a where declared → 46/46; open-defect cells attributable to
risk_documentation: 0 (report total 3/2633, all pre-existing: raw_arch_api ×2, func_complexity_gini — the next contracts' cells); banded against the global median ✓. na_check / ledger_orphan_check / ledger_issue_check clean.One decay note for the Phase 6 sweep:
jcl-steps-have-no-invocation-by-name'srisk_documentationtoken died (jcl reads 100, in band, under the new equation) — scope-down candidate, baselined, advisory.Phase 5 (real-code before/after on curl + cics-genapp + the crucible; decide D5) is next once #120 merges.
squid-protocol commentedon Sep 10, 2026
claiming Phase 5 — Fable 5 — before/after on curl + cics-genapp + the language-crucible; D5 decided on the evidence, comment to follow here
squid-protocol commentedon Sep 10, 2026
status Phase 5: done — the before/after, and D5 is confirmed: no damping for one-unit files. Corpus PR keyword-rosetta#121 (incidence row) open; CI pending.
Before/after (engine ef9b8e6 vs 90ddb3b, full precision)
language-crucible (from the two golden-master versions, 2649 files):
.kshwrappers,alonesym-obj.asm— several now 0 or n/a). The after list is the heaviest genuinely undocumented surface: gnucobol'sparser.y(15,682 LOC, 1,148 units), cpythontypeobject.c(418 units), racket internals, exiftool.curl (696 → 521 scored + 175 n/a):
include/curl/*.handsrc/tool_*.h— curl's famously well-documented public API headers, flagged as the WORST documentation risk by the api×2 sigmoid. They all read 0 now (every unit documented). The new top-20 leads withtests/ftpserver.pl— the exact file the README already features for the function hitlist — thensrc/tool_getparam.c, the test servers,tool_operate.c: heavy, undocumented, real.cics-genapp (127 → 60 scored + 67 n/a):
lgipdb01,lgapdb01,lgupdb01— the README's featured file). Distribution note: a repo with uniform (non-)documentation and one umbrella collapses to a single value by construction; the differentiation is the hitlist ordering, which mass provides.D5 (one-unit files: 0 or 100, no damping)
No one-unit file reaches any top-20: the mass/LOC-weighted ordering keeps them out (curl's heaviest one-unit-at-100 has mass 185 vs a top-20 floor of 780), and the roll-ups are mass-weighted as D5 assumed. The heaviest one-unit case anywhere —
c/micropython/vm.c, 1,151 LOC in one unit at 100 — is a correct flag (a giant undocumented VM interpreter loop), not noise. D5 confirmed as written; no damping issue filed.Method note: scans via the p4 full-precision venv; before = detached worktree at ef9b8e6, after = merged main; hitlists ranked score-desc with structural-mass (DBs) / coding-LOC (golden JSON) tiebreak — the product's own hitlist ranks by impact, which correlates with the mass tiebreak used here.
Phase 6 (closure: contract row →
stated, roadmap tick, README/validation numbers refresh) is the remaining phase.squid-protocol commentedon Sep 10, 2026
claiming Phase 6 — Fable 5 — branch
docs/2908-p6-closure: contract row →stated, roadmap §4 tick, README "Accuracy, measured" + validation.md refreshsquid-protocol commentedon Sep 10, 2026
status Phase 6: done pending CI — PR #2939.
signal_contracts.pygains the score-contract section;risk_documentationis its first entry, stated, rendered intodocs/signal_contracts.md.signal_contract_audit --ciclean (no new findings).contract_roadmap.md§4: first formula marked done;risk_api_exposure,risk_tech_debt,func_complexity_ginicopy this epic's phase list.docs/validation.md: the 2026-09-06 figures (90% / 2.7%; 75% / 3-of-33) retire for the live report — 95% average in-band share, 0.1% open-defect cells (3 of 2,633), weakest metrics named.docs/risk_documentation_contract.md:draft→statedwith the phase record.Epic done-when, verified: open-defect cells attributable to
risk_documentation= 0 (the corpus's 3 remaining opens areraw_arch_api×2 andfunc_complexity_gini— the next contracts' cells); the sheet row isstated; the phase list is copyable as-is (thescore-contract-auditskill names this epic as the worked precedent). Phase 6's checkbox ticks when #2939 merges — after that the epic can close.docs: risk_documentation score contract goes stated -- #2908 Phase 6 (#…
squid-protocol commentedon Sep 11, 2026
Epic complete — ready to close
All phases (0–6) are done and every phase's "done when" gate is met:
\b\w+\b, so a hyphenated or qualified name is never recognised as already-declared (#2731's double count returns for cobol and scheme) #2827, Contextual Baseline Fix reads a weaker dependency resolver than every column it is audited by (haskell) #2871, c api rule counts top-level variable declarations as public surface (a.c reads api 5 for 3 functions; +25.0% sits at the band edge) #2907, raw_arch_api bands a per-file declaration count against a per-function median — one granularity mismatch, three red columns #2771, m4 and makefile have no visibility-export rule, so their api plant cannot name the function it publishes #2872, engine PR 2: per-file dynamism count replaces the flat irc term (child of #2716) #2719. Open-defect cells attributable torisk_documentationare 0.stated—risk_documentationsits inSCORE_CONTRACTS(gitgalaxy/standards/signal_contracts.py) and renders as the firststatedrow of the score-contract section indocs/signal_contracts.md("1 stated, 0 draft").docs/contract_roadmap.md§4 Phase 4: "done (first formula)risk_documentation(Epic: risk_documentation becomes a per-unit coverage ratio (Phase 4 score contract; child of #2812) #2908)". The phase list is the worked precedent the next score contract (risk_api_exposure) copies.One residual (doc hygiene, outside the formal gate)
The README "Accuracy, measured" paragraph and
docs/validation.mdstill carry the 2026-09-06 numbers (90% / 2.7% and 75% / 3 of 33). These want a refresh with live numbers from the next measurement run. This is a documentation-copy follow-up, not part of Phase 6's acceptance criteria — flagging so it isn't lost when this closes.Safe to close as epic complete.
fix(detector): anchor scheme define slices at the outer paren (#2933) (…
feat(report): stop the risk surface claiming more than it measures (#…