@3f-consulting/spec-controller-core 0.1.0-alpha.7 → 0.1.0-alpha.71

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 (257) hide show
  1. package/README.md +3 -3
  2. package/dist/assemble/evidenceReconciliationFromSources.d.ts +5 -40
  3. package/dist/assemble/evidenceReconciliationFromSources.d.ts.map +1 -1
  4. package/dist/assemble/evidenceReconciliationFromSources.js +30 -325
  5. package/dist/assemble/evidenceReconciliationFromSources.js.map +1 -1
  6. package/dist/assemble/renderInput.d.ts +7 -73
  7. package/dist/assemble/renderInput.d.ts.map +1 -1
  8. package/dist/assemble/renderInput.js +3 -45
  9. package/dist/assemble/renderInput.js.map +1 -1
  10. package/dist/census/evidenceObligations.d.ts +11 -46
  11. package/dist/census/evidenceObligations.d.ts.map +1 -1
  12. package/dist/census/evidenceObligations.js +19 -25
  13. package/dist/census/evidenceObligations.js.map +1 -1
  14. package/dist/domain/evidenceOrder.d.ts +3 -0
  15. package/dist/domain/evidenceOrder.d.ts.map +1 -0
  16. package/dist/domain/evidenceOrder.js +15 -0
  17. package/dist/domain/evidenceOrder.js.map +1 -0
  18. package/dist/domain/evidenceReconciliation.d.ts +21 -391
  19. package/dist/domain/evidenceReconciliation.d.ts.map +1 -1
  20. package/dist/domain/evidenceReconciliation.js +87 -382
  21. package/dist/domain/evidenceReconciliation.js.map +1 -1
  22. package/dist/domain/featureCode.d.ts +4 -0
  23. package/dist/domain/featureCode.d.ts.map +1 -0
  24. package/dist/domain/featureCode.js +12 -0
  25. package/dist/domain/featureCode.js.map +1 -0
  26. package/dist/domain/journal.d.ts +8 -393
  27. package/dist/domain/journal.d.ts.map +1 -1
  28. package/dist/domain/journal.js +6 -118
  29. package/dist/domain/journal.js.map +1 -1
  30. package/dist/domain/ledger.d.ts +8 -328
  31. package/dist/domain/ledger.d.ts.map +1 -1
  32. package/dist/domain/ledger.js +23 -326
  33. package/dist/domain/ledger.js.map +1 -1
  34. package/dist/domain/messages.d.ts +213 -0
  35. package/dist/domain/messages.d.ts.map +1 -0
  36. package/dist/domain/messages.js +313 -0
  37. package/dist/domain/messages.js.map +1 -0
  38. package/dist/domain/outcome.d.ts +3 -0
  39. package/dist/domain/outcome.d.ts.map +1 -0
  40. package/dist/domain/outcome.js +2 -0
  41. package/dist/domain/outcome.js.map +1 -0
  42. package/dist/domain/posting.d.ts +8 -105
  43. package/dist/domain/posting.d.ts.map +1 -1
  44. package/dist/domain/posting.js +4 -56
  45. package/dist/domain/posting.js.map +1 -1
  46. package/dist/domain/qualityCheckFault.d.ts +3 -43
  47. package/dist/domain/qualityCheckFault.d.ts.map +1 -1
  48. package/dist/domain/qualityCheckFault.js +0 -24
  49. package/dist/domain/qualityCheckFault.js.map +1 -1
  50. package/dist/domain/qualityCheckReconciliation.d.ts +7 -280
  51. package/dist/domain/qualityCheckReconciliation.d.ts.map +1 -1
  52. package/dist/domain/qualityCheckReconciliation.js +63 -463
  53. package/dist/domain/qualityCheckReconciliation.js.map +1 -1
  54. package/dist/domain/qualityCheckReconciliationFault.d.ts +0 -19
  55. package/dist/domain/qualityCheckReconciliationFault.d.ts.map +1 -1
  56. package/dist/domain/qualityCheckReconciliationFault.js +1 -24
  57. package/dist/domain/qualityCheckReconciliationFault.js.map +1 -1
  58. package/dist/domain/scenarioCodeError.d.ts +16 -0
  59. package/dist/domain/scenarioCodeError.d.ts.map +1 -0
  60. package/dist/domain/scenarioCodeError.js +4 -0
  61. package/dist/domain/scenarioCodeError.js.map +1 -0
  62. package/dist/domain/scenarioReconciliation.d.ts +2 -207
  63. package/dist/domain/scenarioReconciliation.d.ts.map +1 -1
  64. package/dist/domain/scenarioReconciliation.js +4 -101
  65. package/dist/domain/scenarioReconciliation.js.map +1 -1
  66. package/dist/domain/scnAllocation.d.ts +27 -0
  67. package/dist/domain/scnAllocation.d.ts.map +1 -0
  68. package/dist/domain/scnAllocation.js +33 -0
  69. package/dist/domain/scnAllocation.js.map +1 -0
  70. package/dist/domain/verdictCode.d.ts +10 -0
  71. package/dist/domain/verdictCode.d.ts.map +1 -0
  72. package/dist/domain/verdictCode.js +16 -0
  73. package/dist/domain/verdictCode.js.map +1 -0
  74. package/dist/index.d.ts +14 -40
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +8 -88
  77. package/dist/index.js.map +1 -1
  78. package/dist/internal.d.ts +3 -27
  79. package/dist/internal.d.ts.map +1 -1
  80. package/dist/internal.js +3 -94
  81. package/dist/internal.js.map +1 -1
  82. package/dist/lib/bareTerm.d.ts +0 -39
  83. package/dist/lib/bareTerm.d.ts.map +1 -1
  84. package/dist/lib/bareTerm.js +0 -39
  85. package/dist/lib/bareTerm.js.map +1 -1
  86. package/dist/lib/portablePath.d.ts.map +1 -1
  87. package/dist/lib/portablePath.js +2 -16
  88. package/dist/lib/portablePath.js.map +1 -1
  89. package/dist/lib/scnCitation.d.ts +0 -32
  90. package/dist/lib/scnCitation.d.ts.map +1 -1
  91. package/dist/lib/scnCitation.js +2 -42
  92. package/dist/lib/scnCitation.js.map +1 -1
  93. package/dist/model.d.ts +0 -23
  94. package/dist/model.d.ts.map +1 -1
  95. package/dist/model.js +0 -23
  96. package/dist/model.js.map +1 -1
  97. package/dist/parse/checkScriptsRunInCI.d.ts +5 -168
  98. package/dist/parse/checkScriptsRunInCI.d.ts.map +1 -1
  99. package/dist/parse/checkScriptsRunInCI.js +11 -171
  100. package/dist/parse/checkScriptsRunInCI.js.map +1 -1
  101. package/dist/parse/ciStepRecord.d.ts +4 -118
  102. package/dist/parse/ciStepRecord.d.ts.map +1 -1
  103. package/dist/parse/ciStepRecord.js +18 -225
  104. package/dist/parse/ciStepRecord.js.map +1 -1
  105. package/dist/parse/commandRuns.d.ts +0 -39
  106. package/dist/parse/commandRuns.d.ts.map +1 -1
  107. package/dist/parse/commandRuns.js +6 -67
  108. package/dist/parse/commandRuns.js.map +1 -1
  109. package/dist/parse/cucumber.d.ts +0 -53
  110. package/dist/parse/cucumber.d.ts.map +1 -1
  111. package/dist/parse/cucumber.js +42 -146
  112. package/dist/parse/cucumber.js.map +1 -1
  113. package/dist/parse/discoveryScope.d.ts +1 -46
  114. package/dist/parse/discoveryScope.d.ts.map +1 -1
  115. package/dist/parse/discoveryScope.js +2 -57
  116. package/dist/parse/discoveryScope.js.map +1 -1
  117. package/dist/parse/fitnessCitations.d.ts +1 -72
  118. package/dist/parse/fitnessCitations.d.ts.map +1 -1
  119. package/dist/parse/fitnessCitations.js +3 -55
  120. package/dist/parse/fitnessCitations.js.map +1 -1
  121. package/dist/parse/reconcilerSteps.d.ts +0 -46
  122. package/dist/parse/reconcilerSteps.d.ts.map +1 -1
  123. package/dist/parse/reconcilerSteps.js +2 -64
  124. package/dist/parse/reconcilerSteps.js.map +1 -1
  125. package/dist/parse/runningFitnessFunctions.d.ts +9 -0
  126. package/dist/parse/runningFitnessFunctions.d.ts.map +1 -0
  127. package/dist/parse/runningFitnessFunctions.js +57 -0
  128. package/dist/parse/runningFitnessFunctions.js.map +1 -0
  129. package/dist/parse/scenarioCodeErrors.d.ts +11 -0
  130. package/dist/parse/scenarioCodeErrors.d.ts.map +1 -0
  131. package/dist/parse/scenarioCodeErrors.js +78 -0
  132. package/dist/parse/scenarioCodeErrors.js.map +1 -0
  133. package/dist/parse/scenarios.d.ts +3 -228
  134. package/dist/parse/scenarios.d.ts.map +1 -1
  135. package/dist/parse/scenarios.js +20 -457
  136. package/dist/parse/scenarios.js.map +1 -1
  137. package/dist/parse/stepAccounting.d.ts +0 -46
  138. package/dist/parse/stepAccounting.d.ts.map +1 -1
  139. package/dist/parse/stepAccounting.js +1 -55
  140. package/dist/parse/stepAccounting.js.map +1 -1
  141. package/dist/parse/stepExclusions.d.ts +0 -47
  142. package/dist/parse/stepExclusions.d.ts.map +1 -1
  143. package/dist/parse/stepExclusions.js +2 -50
  144. package/dist/parse/stepExclusions.js.map +1 -1
  145. package/dist/parse/stepNames.d.ts +0 -27
  146. package/dist/parse/stepNames.d.ts.map +1 -1
  147. package/dist/parse/stepNames.js +0 -27
  148. package/dist/parse/stepNames.js.map +1 -1
  149. package/dist/parse/targetCorpus.d.ts +1 -78
  150. package/dist/parse/targetCorpus.d.ts.map +1 -1
  151. package/dist/parse/targetCorpus.js +4 -46
  152. package/dist/parse/targetCorpus.js.map +1 -1
  153. package/dist/parse/targetManifest.d.ts +8 -146
  154. package/dist/parse/targetManifest.d.ts.map +1 -1
  155. package/dist/parse/targetManifest.js +48 -198
  156. package/dist/parse/targetManifest.js.map +1 -1
  157. package/dist/parse/vitest.d.ts +0 -60
  158. package/dist/parse/vitest.d.ts.map +1 -1
  159. package/dist/parse/vitest.js +4 -83
  160. package/dist/parse/vitest.js.map +1 -1
  161. package/dist/provenance/provenance.d.ts +2 -110
  162. package/dist/provenance/provenance.d.ts.map +1 -1
  163. package/dist/provenance/provenance.js +2 -58
  164. package/dist/provenance/provenance.js.map +1 -1
  165. package/dist/render/behaviouralRunners.d.ts +0 -29
  166. package/dist/render/behaviouralRunners.d.ts.map +1 -1
  167. package/dist/render/behaviouralRunners.js +6 -69
  168. package/dist/render/behaviouralRunners.js.map +1 -1
  169. package/dist/render/configurationNotRead.d.ts +3 -0
  170. package/dist/render/configurationNotRead.d.ts.map +1 -0
  171. package/dist/render/configurationNotRead.js +17 -0
  172. package/dist/render/configurationNotRead.js.map +1 -0
  173. package/dist/render/discoveryScope.d.ts +0 -37
  174. package/dist/render/discoveryScope.d.ts.map +1 -1
  175. package/dist/render/discoveryScope.js +3 -39
  176. package/dist/render/discoveryScope.js.map +1 -1
  177. package/dist/render/evidenceGrid.d.ts +1 -35
  178. package/dist/render/evidenceGrid.d.ts.map +1 -1
  179. package/dist/render/evidenceGrid.js +21 -103
  180. package/dist/render/evidenceGrid.js.map +1 -1
  181. package/dist/render/excludedSteps.d.ts +0 -26
  182. package/dist/render/excludedSteps.d.ts.map +1 -1
  183. package/dist/render/excludedSteps.js +4 -32
  184. package/dist/render/excludedSteps.js.map +1 -1
  185. package/dist/render/fitnessFunctions.d.ts +3 -0
  186. package/dist/render/fitnessFunctions.d.ts.map +1 -0
  187. package/dist/render/fitnessFunctions.js +12 -0
  188. package/dist/render/fitnessFunctions.js.map +1 -0
  189. package/dist/render/json.d.ts +0 -11
  190. package/dist/render/json.d.ts.map +1 -1
  191. package/dist/render/json.js +0 -11
  192. package/dist/render/json.js.map +1 -1
  193. package/dist/render/markdown.d.ts +1 -55
  194. package/dist/render/markdown.d.ts.map +1 -1
  195. package/dist/render/markdown.js +128 -467
  196. package/dist/render/markdown.js.map +1 -1
  197. package/dist/render/provenanceSection.d.ts +0 -22
  198. package/dist/render/provenanceSection.d.ts.map +1 -1
  199. package/dist/render/provenanceSection.js +10 -53
  200. package/dist/render/provenanceSection.js.map +1 -1
  201. package/dist/render/reconcilerSteps.d.ts +0 -25
  202. package/dist/render/reconcilerSteps.d.ts.map +1 -1
  203. package/dist/render/reconcilerSteps.js +5 -30
  204. package/dist/render/reconcilerSteps.js.map +1 -1
  205. package/dist/render/runVerdict.d.ts +0 -38
  206. package/dist/render/runVerdict.d.ts.map +1 -1
  207. package/dist/render/runVerdict.js +62 -129
  208. package/dist/render/runVerdict.js.map +1 -1
  209. package/dist/render/scenarioCodeErrorsSection.d.ts +3 -0
  210. package/dist/render/scenarioCodeErrorsSection.d.ts.map +1 -0
  211. package/dist/render/scenarioCodeErrorsSection.js +45 -0
  212. package/dist/render/scenarioCodeErrorsSection.js.map +1 -0
  213. package/dist/render/stepAccounting.d.ts +0 -36
  214. package/dist/render/stepAccounting.d.ts.map +1 -1
  215. package/dist/render/stepAccounting.js +5 -49
  216. package/dist/render/stepAccounting.js.map +1 -1
  217. package/dist/render/testVolume.d.ts +0 -25
  218. package/dist/render/testVolume.d.ts.map +1 -1
  219. package/dist/render/testVolume.js +4 -35
  220. package/dist/render/testVolume.js.map +1 -1
  221. package/package.json +4 -1
  222. package/dist/domain/checkCharacteristics.d.ts +0 -88
  223. package/dist/domain/checkCharacteristics.d.ts.map +0 -1
  224. package/dist/domain/checkCharacteristics.js +0 -109
  225. package/dist/domain/checkCharacteristics.js.map +0 -1
  226. package/dist/domain/fitnessChecks.d.ts +0 -79
  227. package/dist/domain/fitnessChecks.d.ts.map +0 -1
  228. package/dist/domain/fitnessChecks.js +0 -81
  229. package/dist/domain/fitnessChecks.js.map +0 -1
  230. package/dist/domain/qualityCheckFaultMessages.d.ts +0 -43
  231. package/dist/domain/qualityCheckFaultMessages.d.ts.map +0 -1
  232. package/dist/domain/qualityCheckFaultMessages.js +0 -38
  233. package/dist/domain/qualityCheckFaultMessages.js.map +0 -1
  234. package/dist/lib/featureCode.d.ts +0 -21
  235. package/dist/lib/featureCode.d.ts.map +0 -1
  236. package/dist/lib/featureCode.js +0 -24
  237. package/dist/lib/featureCode.js.map +0 -1
  238. package/dist/parse/corpusCodeFaults.d.ts +0 -76
  239. package/dist/parse/corpusCodeFaults.d.ts.map +0 -1
  240. package/dist/parse/corpusCodeFaults.js +0 -121
  241. package/dist/parse/corpusCodeFaults.js.map +0 -1
  242. package/dist/parse/corpusFaults.d.ts +0 -124
  243. package/dist/parse/corpusFaults.d.ts.map +0 -1
  244. package/dist/parse/corpusFaults.js +0 -72
  245. package/dist/parse/corpusFaults.js.map +0 -1
  246. package/dist/parse/corpusGrammarFaults.d.ts +0 -71
  247. package/dist/parse/corpusGrammarFaults.d.ts.map +0 -1
  248. package/dist/parse/corpusGrammarFaults.js +0 -292
  249. package/dist/parse/corpusGrammarFaults.js.map +0 -1
  250. package/dist/parse/runningStaticChecks.d.ts +0 -125
  251. package/dist/parse/runningStaticChecks.d.ts.map +0 -1
  252. package/dist/parse/runningStaticChecks.js +0 -316
  253. package/dist/parse/runningStaticChecks.js.map +0 -1
  254. package/dist/render/staticChecks.d.ts +0 -23
  255. package/dist/render/staticChecks.d.ts.map +0 -1
  256. package/dist/render/staticChecks.js +0 -69
  257. package/dist/render/staticChecks.js.map +0 -1
@@ -1,146 +1,43 @@
1
- /**
2
- * THE QUALITY CHECK RECONCILIATION — one reconciling item in the accounting frame: the
3
- * per-evidence-obligation line explaining why obligation and observation disagree. This module
4
- * co-locates the concept's type with its OWN behaviour — the outcome vocabulary, the canon
5
- * descriptions, and `reconcileEvidenceObligation`, the pure derivation that computes a single
6
- * obligation's outcome from the observed evidence (module, not class; the rule lives WITH the
7
- * concept, not in the reconcile orchestration that folds it).
8
- */
9
- import { isStaticCheckObservation, evidenceObligationPairKey, } from "./journal.js";
1
+ import { isFitnessObservation, evidenceObligationPairKey, } from "./journal.js";
10
2
  import { QualityCheckFaultCode } from "./qualityCheckFault.js";
11
3
  import { QualityCheckReconciliationFault } from "./qualityCheckReconciliationFault.js";
12
- // evidence obligation AND no citing evidence observation) — the ONE degenerate Quality Check Reconciliation it
13
- // posts, blank on both sides (label: "Evidence Obligation and Evidence Observation are both
14
- // missing — the Scenario is untagged and uncited"). Detected by the reconciler emitting NOTHING either way (NOT a
15
- // hard evidenceObligations.length === 0 map — a zero-evidence obligation scenario cited by a stray
16
- // credit is `no-evidence-obligation`, @SCN-LDG-010). Retires the zero-evidence-scenario warning (@SCN-WRN-001
17
- // Update): the zero-evidence scenario is now this out-of-balance row, never a separate axis.
18
- // The verdict stays two-valued (out-of-balance VIA this reconciling item, never a third verdict).
19
- /**
20
- * The canon description for each reconciling-item Outcome — the human-readable gloss a
21
- * markdown reader needs (the bare handle means nothing in a CI log). SINGLE SOURCE: the
22
- * markdown renderer renders this description in place of the handle EVERYWHERE an outcome
23
- * surfaces to a reader — the Evidence-reconciliation summary's `↳` decomposition rows, the
24
- * per-scenario reconciling-items list, and the lint-obligation table's Outcome column. The
25
- * JSON keeps the bare handle (the machine axis; a programmatic consumer uses it).
26
- *
27
- * Keyed by EVERY `Outcome` (a `Record<Outcome, …>`), so a new outcome cannot be added to the
28
- * union without a description here — the description can never silently diverge from the
29
- * outcome it names (the drift class this project has been burned by: the census, the views).
30
- * These phrasings ARE the original canon — later abbreviated to the handles; 3F-1626 restores
31
- * them. `satisfied` (the balanced outcome, excluded from the out-of-balance decomposition)
32
- * carries one for totality's sake.
33
- *
34
- * ⚠ KEY ORDER IS LOAD-BEARING (3F-1806). `OUT_OF_BALANCE_OUTCOMES` is DERIVED from these keys,
35
- * and the renderer walks that list to emit the decomposition rows — so this map's key order IS
36
- * the report's row order (`missing` first: the headline cause, the one carrying the bare-level
37
- * split as Detail). Reordering these keys is a REPORT CHANGE, not a cosmetic tidy. The order
38
- * follows canon, grouped by reconciling-item shape: a DEBIT block (missing, failing, watermelon,
39
- * no-scn-code), a CREDIT block (cites-unknown-scenario, cites-no-scenario, no-evidence-obligation), then
40
- * NEITHER (broken, both-missing). @SCN-RPT-019 now pins this SEQUENCE against the rendered `↳`
41
- * rows (3F-1926), so a reorder reds — the exhaustiveness guard alone proved MEMBERSHIP, not
42
- * order, and used not to catch one (which is how the order drifted before it was pinned).
43
- */
44
4
  export const OUTCOME_DESCRIPTION = {
45
5
  satisfied: "Evidence Observation satisfies its Evidence Obligation",
46
6
  missing: "Evidence Observation is missing",
47
7
  failing: "Evidence Observation is failing",
48
8
  watermelon: "Evidence Observation is passing but fabricated",
49
- // The DEBIT TWIN of `cites-no-scenario` (@SCN-LDG-021): same missing LINK, opposite side — the
50
- // scenario is real and ORDINARY, but carries no @SCN for an observation to link to.
51
9
  "no-scn-code": "Evidence Obligation has no @SCN code for an Evidence Observation to link to",
10
+ "duplicate-scn-code": "Evidence Obligation's @SCN code is carried by another Scenario",
52
11
  "cites-unknown-scenario": "Evidence Observation cites an unknown Scenario",
53
12
  "cites-no-scenario": "Evidence Observation cites no Scenario",
54
- // CANON VERBATIM — double-entry-manifesto.md:42, the Outcome column. The gloss is the outcome
55
- // SENTENCE, canon's checkably-true half where the handle is only a label, so it states the
56
- // credit-side fact rather than echoing the handle's root. 3F-2308 landed the sentence; 3F-2300
57
- // then renamed the HANDLE to match it — the old one was the last survivor of a word canon has
58
- // retired — moving the emitted `byOutcome` key and every @SCN-LDG scenario that reads it.
59
13
  "no-evidence-obligation": "Evidence Observation cites a Scenario with no matching Evidence Obligation",
60
- // GENERIC over its producers (@SCN-LDG-017 + @SCN-LDG-019, 3F-1654): `broken` has two instances
61
- // — a vitest suite that failed to COLLECT, and a cucumber step two or more step definitions
62
- // match, which therefore never ran. An ambiguous step is neither a "suite" nor a "collection
63
- // error", so the gloss names what BOTH have in common: the proof exists and did not run.
64
- // REMEDY-NEUTRAL by design: what broke is equally a missing dependency, a syntax error, a bad
65
- // import, a top-level throw, or two colliding step definitions — the gloss names the FACT, not a
66
- // guessed cure. Contrast `missing`, whose remedy is "write the test": conflating the two is
67
- // exactly the false-`missing` this outcome retires.
68
14
  broken: "Evidence Observation ATTEMPT errored before it could cite a Scenario (the proving suite failed to collect; behaviour never exercised)",
69
15
  "both-missing": "Evidence Obligation and Evidence Observation are both missing — the Scenario is untagged and uncited",
70
16
  };
71
- /**
72
- * The out-of-balance reconciling-item outcomes, in report reading order — every `Outcome`
73
- * except `satisfied` (the BALANCED outcome, not part of the OUT-of-balance decomposition).
74
- * The renderer walks this to emit the summary's `↳` leaf rows (@SCN-RPT-019), each reconciling
75
- * to the out-of-balance total exactly once (a FLAT list: the old debit/credit/neither grouping
76
- * emitted a `↳ <group>-side` subtotal AND its leaves at the same indent, so a naive column-sum
77
- * double-counted — the live 170 vs 85).
78
- *
79
- * DERIVED from the type-forced `OUTCOME_DESCRIPTION`, never hand-listed (3F-1806). It was a
80
- * hand-maintained `readonly Outcome[]` in the renderer, which accepted a missing union member
81
- * SILENTLY: `broken` (@SCN-LDG-017) joined the union, tallied into `byOutcome`, compiled clean,
82
- * and would have been omitted from the table. Deriving the keys makes that omission IMPOSSIBLE
83
- * rather than merely CAUGHT — a new `Outcome` cannot compile without a description, and the
84
- * moment it has one it renders a leaf row with no change to the renderer.
85
- *
86
- * Lives in the model, not the renderer: which outcomes are out-of-balance is the model's rule,
87
- * not a presentation choice (`entity-owns-its-behaviour`). The @SCN-RPT-019 guard remains as the
88
- * end-to-end backstop — it renders real markdown and reads the rows back, deriving its
89
- * expectation from its OWN type-forced record (`double-entry`, not a re-read of this source).
90
- */
91
17
  export const OUT_OF_BALANCE_OUTCOMES = Object.keys(OUTCOME_DESCRIPTION).filter((outcome) => outcome !== "satisfied");
92
- /**
93
- * THE CANON OUTCOME-SIDE PROFILE (@SCN-LDG-030, 3F-2016) — the (outcome → side-presence) table
94
- * transcribed from canon (`double-entry-manifesto.md` lines 35-44), keyed by EVERY `Outcome` (a
95
- * `Record<Outcome, …>`), so a new outcome cannot join the union without STATING its side
96
- * profile here — the type-forced totality that makes this map the SINGLE SOURCE the invariant is
97
- * checked against (mirroring `OUTCOME_DESCRIPTION`).
98
- *
99
- * `obligation`/`observation` are `"Exists" | "None"` — whether a canon-correct reconciling item
100
- * of that outcome CARRIES the debit (evidence obligation) and the credit (evidence observation)
101
- * side respectively. NOT a naive function of side-presence: `missing` and `no-scn-code` share
102
- * (Exists, None); the three credit outcomes (`cites-unknown-scenario` / `cites-no-scenario` /
103
- * `no-evidence-obligation`) share (None, Exists). So the invariant `makeQualityCheckReconciliation` enforces
104
- * is (outcome, side-presence) CONSISTENCY against this row, never a derivation of outcome FROM the
105
- * sides.
106
- */
107
18
  export const OUTCOME_SIDE_PROFILE = {
108
19
  satisfied: { obligation: "Exists", observation: "Exists" },
109
20
  missing: { obligation: "Exists", observation: "None" },
110
21
  failing: { obligation: "Exists", observation: "Exists" },
111
22
  watermelon: { obligation: "Exists", observation: "Exists" },
112
23
  "no-scn-code": { obligation: "Exists", observation: "None" },
24
+ "duplicate-scn-code": { obligation: "Exists", observation: "None" },
113
25
  "cites-unknown-scenario": { obligation: "None", observation: "Exists" },
114
26
  "cites-no-scenario": { obligation: "None", observation: "Exists" },
115
27
  "no-evidence-obligation": { obligation: "None", observation: "Exists" },
116
28
  broken: { obligation: "None", observation: "None" },
117
29
  "both-missing": { obligation: "None", observation: "None" },
118
30
  };
119
- /**
120
- * THE SMART CONSTRUCTOR (@SCN-LDG-030, 3F-2016) — the ONLY way to mint a
121
- * `QualityCheckReconciliation`. It takes `outcome` as GIVEN (a consistency check, NOT a
122
- * derivation — `missing`/`no-scn-code` share a profile, so the outcome cannot be recovered from
123
- * side-presence alone), looks up the canon row in `OUTCOME_SIDE_PROFILE`, and THROWS if the
124
- * carried sides disagree with it: a divergent (outcome, side-presence) pair — the watermelon class
125
- * this tool exists to catch, in its own output — is UNCONSTRUCTIBLE.
126
- *
127
- * Returns `fields` UNCHANGED (cast to the branded type): the producer's own key order is preserved
128
- * verbatim, so every unchanged row stays byte-identical (no object is reconstructed).
129
- */
130
31
  export function makeQualityCheckReconciliation(fields) {
131
32
  const profile = OUTCOME_SIDE_PROFILE[fields.outcome];
132
- // The DEBIT-side check: the obligation is carried IFF canon says it Exists for this outcome.
133
33
  if ((fields.evidenceObligation !== undefined) !== (profile.obligation === "Exists")) {
134
34
  throw new QualityCheckReconciliationFault({
135
35
  code: QualityCheckFaultCode.ObligationSideDivergesFromCanon,
136
36
  outcome: fields.outcome,
137
37
  required: profile.obligation,
138
- // The ternary STAYS in code (@SCN-MSG-001): it is parameter logic, not prose, so mutation
139
- // still reaches it — and now an assertion does too, which is what kills it.
140
38
  actual: fields.evidenceObligation !== undefined ? "Exists" : "None",
141
39
  });
142
40
  }
143
- // The CREDIT-side check: the observation is carried IFF canon says it Exists for this outcome.
144
41
  if ((fields.evidenceObservation !== undefined) !== (profile.observation === "Exists")) {
145
42
  throw new QualityCheckReconciliationFault({
146
43
  code: QualityCheckFaultCode.ObservationSideDivergesFromCanon,
@@ -151,336 +48,82 @@ export function makeQualityCheckReconciliation(fields) {
151
48
  }
152
49
  return fields;
153
50
  }
154
- /**
155
- * The where-to-fix a credit carries, for the credit-side items that now see BOTH axes.
156
- *
157
- * A static-check observation has no `locator` and never will: it is read from a target's CI
158
- * config and manifest, not from a test file, so there is no file to send the reader to beyond the
159
- * check's own handle — which the item already carries. Reading the field off the union rather
160
- * than off `RuntimeEvidenceObservation` is what let those items widen to the static axis at all
161
- * (@SCN-LNT-001, since a citing static credit can now post to a real or a phantom row).
162
- */
163
51
  function locatorOf(observation) {
164
- return observation.qualityCheck === "lint" ? undefined : observation.locator;
52
+ return observation.qualityCheck === "fitness" ? undefined : observation.locator;
165
53
  }
166
- /**
167
- * The static-check observation answering THIS scenario's `lint` obligation — ONE ROUTE, the
168
- * citation, on both of its terms (@SCN-LNT-001).
169
- *
170
- * THE CREDIT CITES, and it is the same rule the runtime arm applies: an observation citing this
171
- * scenario's `@SCN` answers its obligation. That is what lets a FITNESS obligation reconcile at
172
- * all — its `staticCheck` is the BARE CHECK TAG the scenario carries (`traceability`), while the
173
- * run knows only the runnable handle (`lint:feature-codes`), so nothing matches by name and
174
- * nothing ever could. The citation is the seam that already crossed between them.
175
- *
176
- * AND THE CITATION IS NOT AN ADDRESS ON ITS OWN (@SCN-LNT-012). The `@SCN` says which ROW the
177
- * credit belongs to; it does not say which of that row's obligations it ANSWERS. A stepless
178
- * scenario may carry more than one bare check tag, so a row can raise two fitness obligations and
179
- * be cited by two runs — and matching on the `@SCN` alone hands the first credit found to both,
180
- * silently absorbing the second. Measured: a genuine `typecheck` watermelon read `satisfied` off
181
- * `lint:complexity`'s credit, and the row read `balanced`. So the observation's own BARE CHECK TAG
182
- * is compared too, and it is the observation's `checkTag` — NOT its `staticCheck`. `staticCheck` is
183
- * the runnable handle, a different vocabulary from the obligation's bare tag, and comparing those
184
- * two is name-match in a new spelling: it agrees only where the words coincide, which is
185
- * `typecheck`, the one unprefixed check. `checkTag` is what the seam resolved, so the comparison is
186
- * like with like.
187
- *
188
- * NAME-MATCH IS GONE FROM HERE TOO (3F-2625). A by-handle fallback stood second, taken only for a
189
- * run that cited NOTHING. 3F-2590 retired the by-name route and removed `posting.ts`'s arm, but
190
- * never touched this file, so this half outlived the retirement it was written to announce — and
191
- * it was LIVE, not dead code. It compared an uncited observation's RUNNABLE handle against the
192
- * obligation's BARE tag, which differ for every prefixed check (`lint:feature-codes` vs
193
- * `traceability`) and are the SAME WORD for a prefix-less one (`typecheck`). On that check an
194
- * uncited credit both posted to suspense AND answered this obligation: the row read `satisfied` on
195
- * evidence that cited nothing, and one observation became the credit side of two items on two rows
196
- * — the exactly-one-posting claim broken by the reconciler rather than by the posting rule.
197
- *
198
- * SO A RUN THAT CITES NOTHING NOW ANSWERS NOTHING. It posts to SUSPENSE (`postingOf`), where a
199
- * check nobody named is meant to surface, and the obligation naming that check reads `missing` —
200
- * an honest pair, both halves loud. Pinned by plant, not by the absence of a symbol:
201
- * `uncitedFitnessCredit.test.ts`, @SCN-USG-001.
202
- *
203
- * ONE WORD IS PASSED, NOT TWO, and it is the BARE CHECK TAG. Every surviving `lint` obligation
204
- * names one: the fitness rule raises it from the tag its stepless scenario carries, and the retired
205
- * `@lint=<handle>` form raises nothing at all (@SCN-LNT-007). The obligation's word is now matched
206
- * ONLY against the observation's own `checkTag` — never against its `staticCheck`, which is the
207
- * runnable handle and a different vocabulary. That comparison was the deleted arm's, and it is what
208
- * made the arm name-match.
209
- *
210
- * Lives beside `reconcileEvidenceObligation` rather than inside it: which observation answers an
211
- * obligation is one idea with a name, and the outcome mapping that follows it is another.
212
- */
213
- function staticCheckObservationFor(scnId, checkTag, evidenceObservations) {
214
- return evidenceObservations.find((o) => isStaticCheckObservation(o) &&
215
- // THE FAIL-SAFE, THE SAME ONE THE RUNTIME MATCH BELOW KEEPS (@SCN-LNT-026). A proof that
216
- // never ran can neither discharge an obligation nor fail it, so a broken credit is excluded
217
- // from the match entirely and its obligation reconciles `missing` by absence — while the
218
- // credit itself posts to suspense, where the reader is told what broke. The exclusion is
219
- // STRUCTURAL rather than incidental: this observation carries its `scnId` and its
220
- // `checkTag`, exactly as cucumber's broken observation carries the `@SCN` its scenario
221
- // cited, so nothing but this clause keeps it out.
54
+ const CREDIT_PRECEDENCE = ["failing", "watermelon", "satisfied"];
55
+ function answerOf(matches, outcomeOf) {
56
+ for (const outcome of CREDIT_PRECEDENCE) {
57
+ const credit = matches.find((match) => outcomeOf(match) === outcome);
58
+ if (credit)
59
+ return { outcome, credit };
60
+ }
61
+ return { outcome: "missing" };
62
+ }
63
+ function fitnessCreditsCiting(scnId, evidenceObservations) {
64
+ return evidenceObservations.filter((o) => isFitnessObservation(o) &&
222
65
  o.broken !== true &&
223
- o.scnId === scnId &&
224
- o.checkTag === checkTag);
66
+ o.scnId === scnId);
225
67
  }
226
- /**
227
- * Reconcile one STATIC-CHECK fitness obligation — the read-over-the-source arm, the fitness
228
- * axis's one route since its measured twin left (3F-3516). Three mappings on the static check
229
- * evidence observation:
230
- * - wired into CI over the real source → satisfied.
231
- * - exists (a package.json script) but NOT wired → THE WATERMELON: green on the surface, red
232
- * within — a static check that guards nothing.
233
- * - absent (no package.json script) → missing: a required ledger entry never recorded, GENUINELY
234
- * DIFFERENT from the watermelon.
235
- *
236
- * NAMED FOR WHAT IT HOLDS. Every surviving fitness obligation carries the BARE CHECK TAG its
237
- * stepless scenario spells (`complexity`), never the runnable handle that invokes it
238
- * (`lint:complexity`) — the retired `@lint=<handle>` form raises nothing (@SCN-LNT-007). The old
239
- * name `guard` read as the handle and was the tag, which is exactly the confusion that let an
240
- * observation's handle be compared against it.
241
- */
242
- function reconcileStaticObligation(scnId, req, evidenceObservations) {
243
- const checkTag = req.staticCheck; // present by construction for a fitness evidence obligation.
244
- const obs = staticCheckObservationFor(scnId, checkTag, evidenceObservations);
245
- let outcome;
246
- // THE CHECK'S OWN VERDICT FIRST, WHERE THE OBSERVATION CARRIED ONE (@SCN-LNT-028), and the order
247
- // is the rule rather than a convenience. `exists` and `enforcedOverRealSource` say a check is
248
- // there and is wired, which is exactly what a check that ran and found violations also is — so
249
- // asked in the other order the wiring facts would answer `satisfied` over a red and the verdict
250
- // would never be reached. It is the same arrangement `broken` takes one function up, for the
251
- // same reason: the fact that reds is the one asked about first.
252
- if (obs?.status === "failed") {
253
- outcome = "failing";
254
- }
255
- else if (obs && obs.enforcedOverRealSource) {
256
- outcome = "satisfied";
257
- }
258
- else if (obs?.exists) {
259
- outcome = "watermelon";
260
- }
261
- else {
262
- outcome = "missing";
263
- }
264
- // The CREDIT side (@SCN-LDG-027/@SCN-LDG-028): the matched static-check observation, carried
265
- // ONLY when it is a REAL observation — `exists:true` (the two-sided `satisfied` / `watermelon`).
266
- // A guard absent from both package.json and CI is FOUND as the negative fact
267
- // `{exists:false}` — an ABSENCE, not an observed piece of evidence — so `missing` carries
268
- // NOTHING on the credit side (Obligation Exists, Observation None; double-entry-manifesto line
269
- // 36), uniform with the runtime `missing` path. `obs?.exists` (not `obs`) is the discriminator:
270
- // `find` locating the absent-guard record must not be mistaken for an observation existing.
68
+ function staticOutcomeOf(credit) {
69
+ if (credit.status === "failed")
70
+ return "failing";
71
+ if (credit.enforcedOverRealSource)
72
+ return "satisfied";
73
+ return credit.exists ? "watermelon" : undefined;
74
+ }
75
+ function reconcileStaticObligation(scnId, obligation, evidenceObservations) {
76
+ const { outcome, credit } = answerOf(fitnessCreditsCiting(scnId, evidenceObservations), staticOutcomeOf);
271
77
  return makeQualityCheckReconciliation({
272
- qualityCheck: "lint",
273
- staticCheck: checkTag,
78
+ qualityCheck: "fitness",
274
79
  outcome,
275
- evidenceObligation: req,
276
- ...(obs?.exists ? { evidenceObservation: obs } : {}),
80
+ evidenceObligation: obligation,
81
+ ...(credit ? { evidenceObservation: credit } : {}),
277
82
  });
278
83
  }
279
- /**
280
- * Reconcile one evidence obligation against the observed evidence, keyed on the
281
- * flat `qualityCheck`. `qualityCheck === "lint"` is the watermelon
282
- * discriminator: it routes to the STATIC-check mapping;
283
- * every other value routes to the RUNTIME-result mapping.
284
- */
285
- export function reconcileEvidenceObligation(scnId, req, evidenceObservations) {
286
- if (req.qualityCheck === "lint") {
287
- return reconcileStaticObligation(scnId, req, evidenceObservations);
288
- }
289
- // A runtime evidence obligation — matched by (scnId, quality-check) among the runtime
290
- // evidence observations (a static check evidence observation carries qualityCheck "lint" and no scnId, so it
291
- // never matches here). present+pass → satisfied; present+fail → failing;
292
- // absent → missing (a required ledger entry never recorded).
293
- //
294
- // THE FAIL-SAFE (@SCN-LDG-017, operator-mandated): `broken` is checked BEFORE `status`. A
295
- // broken observation — one whose PROOF NEVER RAN — is excluded from the match entirely, so it
296
- // can NEVER reconcile as `satisfied` or `failing`: a proof that never ran can neither discharge
297
- // an obligation nor fail it. The obligation then finds no match and reconciles `missing` —
298
- // honest at its own resolution (no evidence citing it arrived); the broken evidence itself
299
- // posts to the SUSPENSE row below, where the reader is sent to fix the harness. The exclusion
300
- // is structural rather than incidental: `broken` rides an OPTIONAL
301
- // FLAG on RuntimeEvidenceObservation, so a broken credit CAN carry an scnId and a status — the
302
- // guard is what makes the fail-safe hold whatever it carries.
303
- const match = evidenceObservations.find((o) =>
304
- // TWO SURVIVING MUTANTS LIVE ON THIS CLAUSE, KEPT DELIBERATELY (3F-2237). Stryker has no
305
- // equivalence detection, so a mutant no test can kill is indistinguishable from one nobody
306
- // has written yet. The argument is recorded here because a reader meeting this line has no
307
- // other way to recover that the clause is redundant ON PURPOSE.
308
- //
309
- // `ConditionalExpression` (this clause → `true`) is redundant FOR ANY INPUT WHATSOEVER.
310
- // The arm is reached only when `req.qualityCheck !== "lint"` (the fitness fork above
311
- // returns first), and `o.qualityCheck === req.qualityCheck` two lines down entails
312
- // `o.qualityCheck !== "lint"` for every `o` that survives the chain. The redundancy is
313
- // LOGICAL, not type-based — no input, however constructed, separates mutant from original.
314
- //
315
- // `StringLiteral` (`"lint"` → `""`) rests on a NARROWER argument, and the difference is the
316
- // point. Mutated, the clause reads `o.qualityCheck !== ""`. On the `"lint"` side the logical
317
- // argument above still covers it — a `"lint"` observation can never satisfy the third clause
318
- // below the fitness fork. The `""` side IS separable: an evidence obligation and a credit
319
- // both carrying `""` reconcile `satisfied` here and `missing` under the mutant. But that
320
- // input is out of domain — no member of `QualityCheck` spells `""`, and nothing in this
321
- // package emits it (every producer is a literal, a closed alternation, or a lookup hit), so
322
- // it is reachable only by casting PAST the parameter type. 3F-2879 drew exactly that line: a
323
- // shape admitted by width subtyping WITH NO CAST is a real published-surface input and kills
324
- // (it killed `locatorOf`, and `fitnessCheckTagOf` with it); an out-of-domain value for a
325
- // typed field is not one, and pinning behaviour for it would assert a case no reader of this
326
- // model can reach.
327
- //
328
- // Kept because it is load-bearing for the compiler — delete it and `pnpm typecheck` reds
329
- // with `TS2339: Property 'broken' does not exist on type 'EvidenceObservation'`, the `"lint"`
330
- // observations never having been narrowed away — and because it states the fail-safe above
331
- // at the point the reader meets it.
332
- o.qualityCheck !== "lint" &&
333
- o.broken !== true &&
334
- o.qualityCheck === req.qualityCheck &&
335
- o.scnId === scnId);
336
- let outcome;
337
- if (!match) {
338
- outcome = "missing";
339
- }
340
- else if (match.status === "passed") {
341
- outcome = "satisfied";
342
- }
343
- else {
344
- outcome = "failing";
84
+ function runtimeOutcomeOf(credit) {
85
+ return credit.status === "passed" ? "satisfied" : "failing";
86
+ }
87
+ export function reconcileEvidenceObligation(scnId, obligation, evidenceObservations) {
88
+ if (obligation.qualityCheck === "fitness") {
89
+ return reconcileStaticObligation(scnId, obligation, evidenceObservations);
345
90
  }
346
- // The CREDIT side (@SCN-LDG-027): the matched runtime observation, present for the two-sided
347
- // outcomes `satisfied` / `failing` and absent for `missing` — the conditional spread keeps
348
- // `missing` observation-free (obligation Exists, observation None).
91
+ const { outcome, credit } = answerOf(evidenceObservations.filter((o) => o.broken !== true &&
92
+ o.qualityCheck === obligation.qualityCheck &&
93
+ o.scnId === scnId), runtimeOutcomeOf);
349
94
  return makeQualityCheckReconciliation({
350
- qualityCheck: req.qualityCheck,
95
+ qualityCheck: obligation.qualityCheck,
351
96
  outcome,
352
- evidenceObligation: req,
353
- ...(match ? { evidenceObservation: match } : {}),
97
+ evidenceObligation: obligation,
98
+ ...(credit ? { evidenceObservation: credit } : {}),
354
99
  });
355
100
  }
356
- // ---------------------------------------------------------------------------
357
- // THE CREDIT-SIDE AND DEGENERATE ITEM RULES (3F-2204) — co-located here beside
358
- // `reconcileEvidenceObligation` because they answer the same question it does: what outcome does
359
- // an entry reconcile to, and which sides does that item carry? `reconcileEvidenceObligation`
360
- // answers it for a DEBIT matched against the observed evidence; these answer it for the entries
361
- // that match no obligation at all (the credit-side outcomes) and for the two degenerate rows
362
- // (no-scn-code, both-missing).
363
- //
364
- // They were inline `.map()` bodies in the `reconcile` fold. Posting-destination left in 3F-2202;
365
- // this is the sibling class — WHAT an entry reconciles to, as opposed to WHERE it lands. Both are
366
- // domain rules, so neither belongs in the orchestration. `reconcile` keeps the walk and builds the
367
- // rows; the outcomes are asked for.
368
- // ---------------------------------------------------------------------------
369
- /**
370
- * What a FITNESS credit calls the check that ran — its bare tag where it has one, else the runnable
371
- * handle that invoked it. `undefined` for a runtime credit, which names a test file instead.
372
- *
373
- * The bare tag is preferred because a reconciling item's `staticCheck` holds the bare tag on the
374
- * debit side, so a reader comparing "the row names complexity" against "a credit for typecheck
375
- * arrived" is comparing like with like. The handle is a fallback rather than a second choice of
376
- * vocabulary: a credit with no tag cites nothing and posts to suspense, so it should not reach a
377
- * real row at all — but if one ever does, naming its handle beats naming nothing.
378
- */
379
- function fitnessCheckNameOf(observation) {
380
- return isStaticCheckObservation(observation)
381
- ? (observation.checkTag ?? observation.staticCheck)
382
- : fitnessCheckTagOf(observation);
101
+ function fitnessFunctionNameOf(observation) {
102
+ return isFitnessObservation(observation)
103
+ ? observation.fitnessFunction
104
+ : undefined;
383
105
  }
384
- /**
385
- * A fitness credit's BARE CHECK TAG, or `undefined` — the term the pair key compares.
386
- *
387
- * Deliberately NO handle fallback, unlike `fitnessCheckNameOf` above. The key is matched against an
388
- * obligation's `staticCheck`, which since the bare-tag unification always holds the BARE tag; a
389
- * handle substituted here would compare `lint:typecheck` against `typecheck` and miss — name-match
390
- * in a new spelling. A credit with no tag keys distinctly from every tagged one, which is what
391
- * stops it matching by having nothing to compare.
392
- */
393
- function fitnessCheckTagOf(observation) {
394
- return isStaticCheckObservation(observation) ? observation.checkTag : undefined;
395
- }
396
- /**
397
- * The NO-EVIDENCE-OBLIGATION items on a real scenario's row (@SCN-LDG-008) — one per credit citing THIS
398
- * scenario at a quality-check it never named. The credit posts to a real row against NO
399
- * evidence obligation, so the item is CREDIT-ONLY (no obligation is in scope).
400
- *
401
- * The pair-check is what distinguishes no-evidence-obligation from a matched credit: a credit at a NAMED
402
- * `(scnId, quality-check)` pair reconciles satisfied/failing via `reconcileEvidenceObligation` and
403
- * is NOT re-listed here. `evidenceObligationPairs` is the journal's rule (`evidenceObligationPairsOf`),
404
- * built once and shared — which is what makes a credit citing a PENDING pair land here rather than
405
- * nowhere (3F-1858: a pending obligation names nothing, so such a credit is no-evidence-obligation exactly
406
- * as when nothing was raised at all, and the run that used to read balanced over it now fails).
407
- */
408
106
  export function noEvidenceObligationReconciliationsOf(scnId, postedCredits, evidenceObligationPairs) {
409
107
  return postedCredits
410
- .filter((o) =>
411
- // THE CREDIT'S OWN CHECK TAG, not the axis (@SCN-LNT-013). Keyed on `lint` alone, a row's
412
- // obligation for one check answered a credit for any other — so a credit the debit match had
413
- // already, correctly, refused by tag was ALSO excluded here and reconciled to nothing.
414
- // Composed by the model's own `evidenceObligationPairKey`, the same spelling the set is
415
- // built with: a key built one way and read another is the same bug in a new spelling.
416
- !evidenceObligationPairs.has(evidenceObligationPairKey(scnId, o.qualityCheck, fitnessCheckTagOf(o))))
108
+ .filter((o) => !evidenceObligationPairs.has(evidenceObligationPairKey(scnId, o.qualityCheck)))
417
109
  .map((o) => makeQualityCheckReconciliation({
418
110
  qualityCheck: o.qualityCheck,
419
111
  outcome: "no-evidence-obligation",
420
- // THE CHECK THAT RAN (@SCN-LNT-013) — a fitness credit's whole where-to-fix, since it has no
421
- // test file to be sent to. Without it the item reads "lint: no evidence obligation" and a
422
- // reader learns only that SOME check arrived uninvited, on a row that may name several.
423
- // Preferring the BARE TAG over the runnable handle is what lets it be read against the row's
424
- // own obligations, which are named by the bare tag — like compared with like. The handle is
425
- // the fallback for a credit that carries no tag, so the item still names something.
426
- ...(fitnessCheckNameOf(o) !== undefined ? { staticCheck: fitnessCheckNameOf(o) } : {}),
427
- // The CREDIT side (@SCN-LDG-027): the citing observation this item reconciles.
112
+ ...(fitnessFunctionNameOf(o) !== undefined ? { fitnessFunction: fitnessFunctionNameOf(o) } : {}),
428
113
  evidenceObservation: o,
429
114
  ...(locatorOf(o) !== undefined ? { locator: locatorOf(o) } : {}),
430
115
  }));
431
116
  }
432
- /**
433
- * The items on a PHANTOM row (@SCN-LDG-006) — one `cites-unknown-scenario` per credit citing an
434
- * `@SCN` no scenario carries, in citation order. CREDIT-ONLY: a phantom row is minted BY a credit,
435
- * so nothing can ever have been named of it, and no obligation is in scope.
436
- *
437
- * THE WHERE-TO-FIX IS THE WHOLE OF WHAT THIS ITEM CAN OFFER, which is why both halves ride through
438
- * (ruling Q5): a phantom item has no obligation to point a reader at instead. A runtime credit
439
- * carries its locator (the test file that cited); a FITNESS credit carries the CHECK THAT RAN —
440
- * it is read from a target's config, not from a test file, so it has no locator and the handle is
441
- * all there is. That half became reachable when the fitness axis started citing (@SCN-LNT-009);
442
- * the suspense arm below already carried it, and the two unmatched-credit destinations must not
443
- * tell a reader different amounts about the same class of fault.
444
- */
445
117
  export function citesUnknownScenarioReconciliationsOf(credits) {
446
118
  return credits.map((o) => makeQualityCheckReconciliation({
447
119
  qualityCheck: o.qualityCheck,
448
- // THE CHECK THAT RAN is the fitness credit's whole where-to-fix — it has no test file to be
449
- // sent to. For a static run that is its runnable handle.
450
- ...(isStaticCheckObservation(o) ? { staticCheck: o.staticCheck } : {}),
120
+ ...(isFitnessObservation(o) ? { fitnessFunction: o.fitnessFunction } : {}),
451
121
  outcome: "cites-unknown-scenario",
452
122
  evidenceObservation: o,
453
123
  ...(locatorOf(o) !== undefined ? { locator: locatorOf(o) } : {}),
454
124
  }));
455
125
  }
456
- /**
457
- * The items on the SUSPENSE row (@SCN-LDG-007) — every entry that names no scenario at all,
458
- * in the ONE order canon fixes.
459
- *
460
- * THE ORDER IS THE RULE, not an accident of assembly. `broken` comes FIRST because a broken run's
461
- * other suspense items are downstream noise: the reader must be sent to the harness before being
462
- * asked to read anything the harness failed to produce.
463
- *
464
- * THE SPLIT (@SCN-LDG-017) is why `broken` and `cites-no-scenario` are separate arms rather than
465
- * one map over "everything uncited". Both land here because they name no scenario, but they are
466
- * distinguished by errored-vs-completed and their remedies differ: a `cites-no-scenario` test RAN
467
- * and forgot its citation (add the `@SCN`); a `broken` suite never ran at all (fix the harness).
468
- * Folding the second into the first would hide a collection failure behind the wrong cure.
469
- *
470
- * The three arms carry different payloads, each for a stated reason:
471
- * - `broken` carries its WHERE-to-fix (locator) and its WHAT-broke (the runner's message), and
472
- * NO `evidenceObservation` — a proof that never ran is not an observed piece of evidence.
473
- * - an uncited runtime credit carries the observation it reconciles, plus its locator.
474
- * - a no-evidence-obligation static check carries its handle and its observation; it has no locator (a
475
- * static check is read from config, not from a test file).
476
- *
477
- * THE BROKEN STREAM TAKES EITHER AXIS SINCE @SCN-LNT-026, AND THE ARM NEEDED NO BRANCH FOR IT.
478
- * A converted static check that did not run carries the same two payload halves under the same
479
- * two names, so the map reads what the observation holds exactly as it does for a vitest suite
480
- * that failed to collect. What differs is only the `qualityCheck` the item then carries — `lint`
481
- * rather than a runtime tier — which is read off the observation and not decided here.
482
- */
483
- export function suspenseReconciliationsOf(brokenEvidence, uncited, noEvidenceObligationGuards) {
126
+ export function suspenseReconciliationsOf(brokenEvidence, uncited, noEvidenceObligationFitnessFunctions) {
484
127
  return [
485
128
  ...brokenEvidence.map((o) => makeQualityCheckReconciliation({
486
129
  qualityCheck: o.qualityCheck,
@@ -494,73 +137,30 @@ export function suspenseReconciliationsOf(brokenEvidence, uncited, noEvidenceObl
494
137
  evidenceObservation: o,
495
138
  ...(o.locator !== undefined ? { locator: o.locator } : {}),
496
139
  })),
497
- ...noEvidenceObligationGuards.map((o) => makeQualityCheckReconciliation({
498
- qualityCheck: "lint",
499
- // The check that ran: a static run knows its runnable handle.
500
- staticCheck: o.staticCheck,
140
+ ...noEvidenceObligationFitnessFunctions.map((o) => makeQualityCheckReconciliation({
141
+ qualityCheck: "fitness",
142
+ fitnessFunction: o.fitnessFunction,
501
143
  outcome: "cites-no-scenario",
502
144
  evidenceObservation: o,
503
145
  })),
504
146
  ];
505
147
  }
506
- /**
507
- * The item on an UNTAGGED scenario's row (@SCN-LDG-021) — the DEBIT twin of `cites-no-scenario`
508
- * (same missing link, opposite side; suspense stays credit-only).
509
- *
510
- * A scenario carrying no `@SCN` is REAL and ORDINARY — cucumber runs it — but its obligation has no
511
- * CODE for an observation to link to, so it could reconcile NOTHING: not satisfied, not failing,
512
- * not even `missing`, because an obligation never RAISED cannot be ABSENT. Without this item it
513
- * contributed nothing to the verdict and the run went green over work nothing proves.
514
- *
515
- * It carries NO `qualityCheck`: with no code, no observation can link at ANY tier, so the missing
516
- * link is SCENARIO-scope, not tier-scope (the shape `both-missing` also uses). The synthesised
517
- * scenario-scope obligation — an empty `{}` — lives ONLY on this item; the row's own
518
- * `evidenceObligations` stays empty (canon citation: double-entry-manifesto line 39, Obligation
519
- * Exists / Observation None).
520
- */
521
148
  export function noScnCodeReconciliations() {
522
149
  return [makeQualityCheckReconciliation({ outcome: "no-scn-code", evidenceObligation: {} })];
523
150
  }
524
- /**
525
- * The degenerate BOTH-MISSING item (@SCN-LDG-009) — the NEITHER-side outcome, blank on both sides:
526
- * no `qualityCheck` (it is neither a debit obligation nor a credit observation), no carried entry.
527
- * A scenario that reconciled to zero rows on BOTH sides posts exactly one of these, and is
528
- * out-of-balance through it.
529
- *
530
- * Construction lives here with the item; the rule for WHEN a row carries it instead of its sub-rows
531
- * is the ROW's, and lives on `scenarioReconciliation` (`qualityCheckReconciliationsOf`).
532
- */
151
+ export function duplicateScnCodeReconciliationsOf(obligations) {
152
+ const firstNaming = new Map();
153
+ for (const obligation of obligations) {
154
+ if (obligation.qualityCheck !== undefined && !firstNaming.has(obligation.qualityCheck)) {
155
+ firstNaming.set(obligation.qualityCheck, obligation);
156
+ }
157
+ }
158
+ if (firstNaming.size === 0) {
159
+ return [makeQualityCheckReconciliation({ outcome: "duplicate-scn-code", evidenceObligation: {} })];
160
+ }
161
+ return [...firstNaming].map(([qualityCheck, evidenceObligation]) => makeQualityCheckReconciliation({ qualityCheck, outcome: "duplicate-scn-code", evidenceObligation }));
162
+ }
533
163
  export function bothMissingReconciliation() {
534
164
  return makeQualityCheckReconciliation({ outcome: "both-missing" });
535
165
  }
536
- /**
537
- * The item on the row a CONFIGURED ROW WITH NO SCENARIO earns (@SCN-LNT-027, 3F-3472) — canon's
538
- * `missing`: Obligation Exists, Observation None.
539
- *
540
- * NO NEW OUTCOME, AND THAT IS THE ARGUMENT RATHER THAN THE CONVENIENCE. A row the manifest declares
541
- * and no scenario specifies is a debit nothing observed, which is what `missing` already means and
542
- * what `features/fitness-checks.feature` promises in those words. Minting a fourth corpus-shape
543
- * outcome would put a second sentence beside a row canon already has a sentence for.
544
- *
545
- * THE OBLIGATION IS THE ROW'S OWN, and it is REAL — `{ qualityCheck: "lint", staticCheck: <the
546
- * characteristic> }`, the same shape a converted scenario raises. That is what separates this from
547
- * the uncoded-work item beside it: THAT one synthesises an empty `{}` because an untagged scenario
548
- * names no check at all, and this row names exactly one. So the row posts it as a debit too, and
549
- * the summary counts the obligation the manifest raised.
550
- *
551
- * NOTHING HERE CAN DISCHARGE IT, and that is structural rather than asserted: no observation is
552
- * offered to this item at all, and the minted row's `@SCN` is not a member of the set the citation
553
- * router posts credits against. A credit citing the row's code still mints a phantom row
554
- * (@SCN-LNT-009); there is no route by which it reaches this one.
555
- */
556
- export function unspecifiedRowReconciliations(characteristic) {
557
- return [
558
- makeQualityCheckReconciliation({
559
- qualityCheck: "lint",
560
- staticCheck: characteristic,
561
- outcome: "missing",
562
- evidenceObligation: { qualityCheck: "lint", staticCheck: characteristic },
563
- }),
564
- ];
565
- }
566
166
  //# sourceMappingURL=qualityCheckReconciliation.js.map