@transitrix/diagrams 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (541) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +47 -0
  3. package/dist/__tests__/edge-path.test.d.ts +1 -0
  4. package/dist/__tests__/edge-path.test.js +39 -0
  5. package/dist/__tests__/scope.test.d.ts +1 -0
  6. package/dist/__tests__/scope.test.js +23 -0
  7. package/dist/__tests__/yaml-normalize.test.d.ts +1 -0
  8. package/dist/__tests__/yaml-normalize.test.js +148 -0
  9. package/dist/activities/__tests__/cpm.test.d.ts +1 -0
  10. package/dist/activities/__tests__/cpm.test.js +146 -0
  11. package/dist/activities/__tests__/gantt.test.d.ts +1 -0
  12. package/dist/activities/__tests__/gantt.test.js +224 -0
  13. package/dist/activities/__tests__/layout.test.d.ts +1 -0
  14. package/dist/activities/__tests__/layout.test.js +98 -0
  15. package/dist/activities/__tests__/validate.test.d.ts +1 -0
  16. package/dist/activities/__tests__/validate.test.js +363 -0
  17. package/dist/activities/cpm.d.ts +2 -0
  18. package/dist/activities/cpm.js +86 -0
  19. package/dist/activities/gantt.d.ts +6 -0
  20. package/dist/activities/gantt.js +250 -0
  21. package/dist/activities/index.d.ts +5 -0
  22. package/dist/activities/index.js +4 -0
  23. package/dist/activities/layout.d.ts +2 -0
  24. package/dist/activities/layout.js +103 -0
  25. package/dist/activities/types.d.ts +125 -0
  26. package/dist/activities/types.js +1 -0
  27. package/dist/activities/validate.d.ts +2 -0
  28. package/dist/activities/validate.js +326 -0
  29. package/dist/activity-card/__tests__/example.test.d.ts +1 -0
  30. package/dist/activity-card/__tests__/example.test.js +56 -0
  31. package/dist/activity-card/__tests__/layout.test.d.ts +1 -0
  32. package/dist/activity-card/__tests__/layout.test.js +57 -0
  33. package/dist/activity-card/__tests__/resolver.test.d.ts +1 -0
  34. package/dist/activity-card/__tests__/resolver.test.js +146 -0
  35. package/dist/activity-card/__tests__/validate.test.d.ts +1 -0
  36. package/dist/activity-card/__tests__/validate.test.js +116 -0
  37. package/dist/activity-card/index.d.ts +5 -0
  38. package/dist/activity-card/index.js +3 -0
  39. package/dist/activity-card/layout.d.ts +7 -0
  40. package/dist/activity-card/layout.js +167 -0
  41. package/dist/activity-card/resolver.d.ts +7 -0
  42. package/dist/activity-card/resolver.js +310 -0
  43. package/dist/activity-card/types.d.ts +175 -0
  44. package/dist/activity-card/types.js +20 -0
  45. package/dist/activity-card/validate.d.ts +3 -0
  46. package/dist/activity-card/validate.js +145 -0
  47. package/dist/actor/__tests__/validate.test.d.ts +1 -0
  48. package/dist/actor/__tests__/validate.test.js +57 -0
  49. package/dist/actor/index.d.ts +3 -0
  50. package/dist/actor/index.js +2 -0
  51. package/dist/actor/types.d.ts +20 -0
  52. package/dist/actor/types.js +4 -0
  53. package/dist/actor/validate.d.ts +2 -0
  54. package/dist/actor/validate.js +34 -0
  55. package/dist/applications/__tests__/validate.test.d.ts +1 -0
  56. package/dist/applications/__tests__/validate.test.js +223 -0
  57. package/dist/applications/index.d.ts +2 -0
  58. package/dist/applications/index.js +1 -0
  59. package/dist/applications/types.d.ts +39 -0
  60. package/dist/applications/types.js +1 -0
  61. package/dist/applications/validate.d.ts +3 -0
  62. package/dist/applications/validate.js +109 -0
  63. package/dist/assertion/__tests__/example.test.d.ts +1 -0
  64. package/dist/assertion/__tests__/example.test.js +32 -0
  65. package/dist/assertion/__tests__/validate.test.d.ts +1 -0
  66. package/dist/assertion/__tests__/validate.test.js +130 -0
  67. package/dist/assertion/index.d.ts +4 -0
  68. package/dist/assertion/index.js +2 -0
  69. package/dist/assertion/types.d.ts +43 -0
  70. package/dist/assertion/types.js +7 -0
  71. package/dist/assertion/validate.d.ts +9 -0
  72. package/dist/assertion/validate.js +137 -0
  73. package/dist/blocks/__tests__/layout.test.d.ts +1 -0
  74. package/dist/blocks/__tests__/layout.test.js +153 -0
  75. package/dist/blocks/__tests__/validate.test.d.ts +1 -0
  76. package/dist/blocks/__tests__/validate.test.js +235 -0
  77. package/dist/blocks/index.d.ts +4 -0
  78. package/dist/blocks/index.js +2 -0
  79. package/dist/blocks/layout.d.ts +4 -0
  80. package/dist/blocks/layout.js +145 -0
  81. package/dist/blocks/types.d.ts +56 -0
  82. package/dist/blocks/types.js +1 -0
  83. package/dist/blocks/validate.d.ts +9 -0
  84. package/dist/blocks/validate.js +186 -0
  85. package/dist/capability-map/__tests__/validate.test.d.ts +1 -0
  86. package/dist/capability-map/__tests__/validate.test.js +212 -0
  87. package/dist/capability-map/index.d.ts +2 -0
  88. package/dist/capability-map/index.js +1 -0
  89. package/dist/capability-map/types.d.ts +26 -0
  90. package/dist/capability-map/types.js +1 -0
  91. package/dist/capability-map/validate.d.ts +3 -0
  92. package/dist/capability-map/validate.js +103 -0
  93. package/dist/change/__tests__/validate.test.d.ts +1 -0
  94. package/dist/change/__tests__/validate.test.js +73 -0
  95. package/dist/change/index.d.ts +2 -0
  96. package/dist/change/index.js +1 -0
  97. package/dist/change/types.d.ts +17 -0
  98. package/dist/change/types.js +3 -0
  99. package/dist/change/validate.d.ts +2 -0
  100. package/dist/change/validate.js +57 -0
  101. package/dist/compliance/__tests__/classify.test.d.ts +1 -0
  102. package/dist/compliance/__tests__/classify.test.js +24 -0
  103. package/dist/compliance/__tests__/confidence.test.d.ts +1 -0
  104. package/dist/compliance/__tests__/confidence.test.js +78 -0
  105. package/dist/compliance/__tests__/coverage-metric.test.d.ts +1 -0
  106. package/dist/compliance/__tests__/coverage-metric.test.js +164 -0
  107. package/dist/compliance/__tests__/demo-scenario.test.d.ts +1 -0
  108. package/dist/compliance/__tests__/demo-scenario.test.js +226 -0
  109. package/dist/compliance/__tests__/example.test.d.ts +1 -0
  110. package/dist/compliance/__tests__/example.test.js +51 -0
  111. package/dist/compliance/__tests__/gap-report.test.d.ts +1 -0
  112. package/dist/compliance/__tests__/gap-report.test.js +113 -0
  113. package/dist/compliance/__tests__/html.test.d.ts +1 -0
  114. package/dist/compliance/__tests__/html.test.js +122 -0
  115. package/dist/compliance/__tests__/impact.test.d.ts +1 -0
  116. package/dist/compliance/__tests__/impact.test.js +410 -0
  117. package/dist/compliance/__tests__/markdown.test.d.ts +1 -0
  118. package/dist/compliance/__tests__/markdown.test.js +85 -0
  119. package/dist/compliance/__tests__/views.test.d.ts +1 -0
  120. package/dist/compliance/__tests__/views.test.js +59 -0
  121. package/dist/compliance/classify.d.ts +27 -0
  122. package/dist/compliance/classify.js +60 -0
  123. package/dist/compliance/confidence.d.ts +27 -0
  124. package/dist/compliance/confidence.js +43 -0
  125. package/dist/compliance/coverage-metric.d.ts +74 -0
  126. package/dist/compliance/coverage-metric.js +181 -0
  127. package/dist/compliance/gap-report.d.ts +20 -0
  128. package/dist/compliance/gap-report.js +64 -0
  129. package/dist/compliance/html.d.ts +22 -0
  130. package/dist/compliance/html.js +256 -0
  131. package/dist/compliance/impact.d.ts +251 -0
  132. package/dist/compliance/impact.js +464 -0
  133. package/dist/compliance/index.d.ts +16 -0
  134. package/dist/compliance/index.js +9 -0
  135. package/dist/compliance/markdown.d.ts +18 -0
  136. package/dist/compliance/markdown.js +129 -0
  137. package/dist/compliance/reverse-index.d.ts +8 -0
  138. package/dist/compliance/reverse-index.js +30 -0
  139. package/dist/compliance/types.d.ts +98 -0
  140. package/dist/compliance/types.js +5 -0
  141. package/dist/compliance/views.d.ts +14 -0
  142. package/dist/compliance/views.js +35 -0
  143. package/dist/compliance-matrix/__tests__/build.test.d.ts +1 -0
  144. package/dist/compliance-matrix/__tests__/build.test.js +183 -0
  145. package/dist/compliance-matrix/__tests__/example.test.d.ts +1 -0
  146. package/dist/compliance-matrix/__tests__/example.test.js +49 -0
  147. package/dist/compliance-matrix/build.d.ts +18 -0
  148. package/dist/compliance-matrix/build.js +0 -0
  149. package/dist/compliance-matrix/index.d.ts +2 -0
  150. package/dist/compliance-matrix/index.js +1 -0
  151. package/dist/compliance-matrix/types.d.ts +86 -0
  152. package/dist/compliance-matrix/types.js +8 -0
  153. package/dist/confidence/__tests__/score.test.d.ts +1 -0
  154. package/dist/confidence/__tests__/score.test.js +188 -0
  155. package/dist/confidence/index.d.ts +2 -0
  156. package/dist/confidence/index.js +1 -0
  157. package/dist/confidence/score.d.ts +62 -0
  158. package/dist/confidence/score.js +215 -0
  159. package/dist/confidence/types.d.ts +79 -0
  160. package/dist/confidence/types.js +6 -0
  161. package/dist/edge-path.d.ts +18 -0
  162. package/dist/edge-path.js +35 -0
  163. package/dist/element-envelope.d.ts +15 -0
  164. package/dist/element-envelope.js +55 -0
  165. package/dist/factor/__tests__/validate.test.d.ts +1 -0
  166. package/dist/factor/__tests__/validate.test.js +83 -0
  167. package/dist/factor/index.d.ts +3 -0
  168. package/dist/factor/index.js +2 -0
  169. package/dist/factor/types.d.ts +23 -0
  170. package/dist/factor/types.js +6 -0
  171. package/dist/factor/validate.d.ts +2 -0
  172. package/dist/factor/validate.js +69 -0
  173. package/dist/fgca/__tests__/chain-table.test.d.ts +1 -0
  174. package/dist/fgca/__tests__/chain-table.test.js +158 -0
  175. package/dist/fgca/__tests__/layout.test.d.ts +1 -0
  176. package/dist/fgca/__tests__/layout.test.js +140 -0
  177. package/dist/fgca/__tests__/parse-canonical.test.d.ts +1 -0
  178. package/dist/fgca/__tests__/parse-canonical.test.js +224 -0
  179. package/dist/fgca/__tests__/preview-layout.test.d.ts +1 -0
  180. package/dist/fgca/__tests__/preview-layout.test.js +106 -0
  181. package/dist/fgca/__tests__/validate.test.d.ts +1 -0
  182. package/dist/fgca/__tests__/validate.test.js +125 -0
  183. package/dist/fgca/chain-table.d.ts +36 -0
  184. package/dist/fgca/chain-table.js +177 -0
  185. package/dist/fgca/index.d.ts +16 -0
  186. package/dist/fgca/index.js +11 -0
  187. package/dist/fgca/layout.d.ts +27 -0
  188. package/dist/fgca/layout.js +156 -0
  189. package/dist/fgca/nodeTypes.d.ts +6 -0
  190. package/dist/fgca/nodeTypes.js +10 -0
  191. package/dist/fgca/nodes/FGCAActivityNode.d.ts +13 -0
  192. package/dist/fgca/nodes/FGCAActivityNode.js +35 -0
  193. package/dist/fgca/nodes/FGCAChangeNode.d.ts +12 -0
  194. package/dist/fgca/nodes/FGCAChangeNode.js +35 -0
  195. package/dist/fgca/nodes/FGCAFactorNode.d.ts +12 -0
  196. package/dist/fgca/nodes/FGCAFactorNode.js +35 -0
  197. package/dist/fgca/nodes/FGCAGoalNode.d.ts +14 -0
  198. package/dist/fgca/nodes/FGCAGoalNode.js +40 -0
  199. package/dist/fgca/parse-canonical.d.ts +58 -0
  200. package/dist/fgca/parse-canonical.js +339 -0
  201. package/dist/fgca/preview-layout.d.ts +87 -0
  202. package/dist/fgca/preview-layout.js +122 -0
  203. package/dist/fgca/types.d.ts +39 -0
  204. package/dist/fgca/types.js +7 -0
  205. package/dist/fgca/validate.d.ts +14 -0
  206. package/dist/fgca/validate.js +108 -0
  207. package/dist/geometry.d.ts +7 -0
  208. package/dist/geometry.js +2 -0
  209. package/dist/goals/__tests__/layout.test.d.ts +1 -0
  210. package/dist/goals/__tests__/layout.test.js +153 -0
  211. package/dist/goals/__tests__/mutations.test.d.ts +1 -0
  212. package/dist/goals/__tests__/mutations.test.js +95 -0
  213. package/dist/goals/__tests__/parse-canonical.test.d.ts +1 -0
  214. package/dist/goals/__tests__/parse-canonical.test.js +129 -0
  215. package/dist/goals/__tests__/validate.test.d.ts +1 -0
  216. package/dist/goals/__tests__/validate.test.js +183 -0
  217. package/dist/goals/index.d.ts +6 -0
  218. package/dist/goals/index.js +4 -0
  219. package/dist/goals/layout.d.ts +11 -0
  220. package/dist/goals/layout.js +147 -0
  221. package/dist/goals/mutations.d.ts +6 -0
  222. package/dist/goals/mutations.js +116 -0
  223. package/dist/goals/parse-canonical.d.ts +29 -0
  224. package/dist/goals/parse-canonical.js +257 -0
  225. package/dist/goals/types.d.ts +71 -0
  226. package/dist/goals/types.js +1 -0
  227. package/dist/goals/validate.d.ts +2 -0
  228. package/dist/goals/validate.js +122 -0
  229. package/dist/index.d.ts +23 -0
  230. package/dist/index.js +23 -0
  231. package/dist/issues/__tests__/layout.test.d.ts +1 -0
  232. package/dist/issues/__tests__/layout.test.js +62 -0
  233. package/dist/issues/__tests__/validate.test.d.ts +1 -0
  234. package/dist/issues/__tests__/validate.test.js +82 -0
  235. package/dist/issues/index.d.ts +4 -0
  236. package/dist/issues/index.js +2 -0
  237. package/dist/issues/layout.d.ts +10 -0
  238. package/dist/issues/layout.js +82 -0
  239. package/dist/issues/types.d.ts +61 -0
  240. package/dist/issues/types.js +6 -0
  241. package/dist/issues/validate.d.ts +3 -0
  242. package/dist/issues/validate.js +143 -0
  243. package/dist/process-blueprint/__tests__/compliance-lane.test.d.ts +1 -0
  244. package/dist/process-blueprint/__tests__/compliance-lane.test.js +272 -0
  245. package/dist/process-blueprint/__tests__/layout.test.d.ts +1 -0
  246. package/dist/process-blueprint/__tests__/layout.test.js +159 -0
  247. package/dist/process-blueprint/__tests__/validate.test.d.ts +1 -0
  248. package/dist/process-blueprint/__tests__/validate.test.js +280 -0
  249. package/dist/process-blueprint/index.d.ts +4 -0
  250. package/dist/process-blueprint/index.js +2 -0
  251. package/dist/process-blueprint/layout.d.ts +2 -0
  252. package/dist/process-blueprint/layout.js +416 -0
  253. package/dist/process-blueprint/types.d.ts +220 -0
  254. package/dist/process-blueprint/types.js +1 -0
  255. package/dist/process-blueprint/validate.d.ts +3 -0
  256. package/dist/process-blueprint/validate.js +165 -0
  257. package/dist/process-map/__tests__/validate.test.d.ts +1 -0
  258. package/dist/process-map/__tests__/validate.test.js +199 -0
  259. package/dist/process-map/index.d.ts +2 -0
  260. package/dist/process-map/index.js +1 -0
  261. package/dist/process-map/types.d.ts +32 -0
  262. package/dist/process-map/types.js +1 -0
  263. package/dist/process-map/validate.d.ts +3 -0
  264. package/dist/process-map/validate.js +115 -0
  265. package/dist/products/__tests__/validate.test.d.ts +1 -0
  266. package/dist/products/__tests__/validate.test.js +210 -0
  267. package/dist/products/index.d.ts +2 -0
  268. package/dist/products/index.js +1 -0
  269. package/dist/products/types.d.ts +28 -0
  270. package/dist/products/types.js +1 -0
  271. package/dist/products/validate.d.ts +3 -0
  272. package/dist/products/validate.js +93 -0
  273. package/dist/repo-validate/__tests__/validate-repo.test.d.ts +1 -0
  274. package/dist/repo-validate/__tests__/validate-repo.test.js +132 -0
  275. package/dist/repo-validate/index.d.ts +2 -0
  276. package/dist/repo-validate/index.js +1 -0
  277. package/dist/repo-validate/types.d.ts +34 -0
  278. package/dist/repo-validate/types.js +14 -0
  279. package/dist/repo-validate/validate-repo.d.ts +8 -0
  280. package/dist/repo-validate/validate-repo.js +189 -0
  281. package/dist/requirement/__tests__/example.test.d.ts +1 -0
  282. package/dist/requirement/__tests__/example.test.js +21 -0
  283. package/dist/requirement/__tests__/validate.test.d.ts +1 -0
  284. package/dist/requirement/__tests__/validate.test.js +80 -0
  285. package/dist/requirement/index.d.ts +4 -0
  286. package/dist/requirement/index.js +2 -0
  287. package/dist/requirement/types.d.ts +25 -0
  288. package/dist/requirement/types.js +4 -0
  289. package/dist/requirement/validate.d.ts +7 -0
  290. package/dist/requirement/validate.js +81 -0
  291. package/dist/scenarios/__tests__/validate.test.d.ts +1 -0
  292. package/dist/scenarios/__tests__/validate.test.js +183 -0
  293. package/dist/scenarios/index.d.ts +2 -0
  294. package/dist/scenarios/index.js +1 -0
  295. package/dist/scenarios/types.d.ts +45 -0
  296. package/dist/scenarios/types.js +1 -0
  297. package/dist/scenarios/validate.d.ts +3 -0
  298. package/dist/scenarios/validate.js +110 -0
  299. package/dist/schema-version.d.ts +16 -0
  300. package/dist/schema-version.js +16 -0
  301. package/dist/scope.d.ts +23 -0
  302. package/dist/scope.js +38 -0
  303. package/dist/stakeholder/__tests__/validate.test.d.ts +1 -0
  304. package/dist/stakeholder/__tests__/validate.test.js +86 -0
  305. package/dist/stakeholder/index.d.ts +3 -0
  306. package/dist/stakeholder/index.js +2 -0
  307. package/dist/stakeholder/types.d.ts +24 -0
  308. package/dist/stakeholder/types.js +5 -0
  309. package/dist/stakeholder/validate.d.ts +2 -0
  310. package/dist/stakeholder/validate.js +58 -0
  311. package/dist/target-state/__tests__/validate.test.d.ts +1 -0
  312. package/dist/target-state/__tests__/validate.test.js +62 -0
  313. package/dist/target-state/index.d.ts +2 -0
  314. package/dist/target-state/index.js +1 -0
  315. package/dist/target-state/types.d.ts +18 -0
  316. package/dist/target-state/types.js +7 -0
  317. package/dist/target-state/validate.d.ts +2 -0
  318. package/dist/target-state/validate.js +61 -0
  319. package/dist/theme/index.d.ts +4 -0
  320. package/dist/theme/index.js +2 -0
  321. package/dist/theme/themes.d.ts +16 -0
  322. package/dist/theme/themes.js +213 -0
  323. package/dist/theme/tokens.d.ts +158 -0
  324. package/dist/theme/tokens.js +105 -0
  325. package/dist/typed-id.d.ts +20 -0
  326. package/dist/typed-id.js +38 -0
  327. package/dist/validation-types.d.ts +34 -0
  328. package/dist/validation-types.js +1 -0
  329. package/dist/webview/__tests__/entry-notations.test.d.ts +1 -0
  330. package/dist/webview/__tests__/entry-notations.test.js +62 -0
  331. package/dist/webview/__tests__/entry.test.d.ts +1 -0
  332. package/dist/webview/__tests__/entry.test.js +56 -0
  333. package/dist/webview/__tests__/render-goals.test.d.ts +1 -0
  334. package/dist/webview/__tests__/render-goals.test.js +65 -0
  335. package/dist/webview/entry.d.ts +38 -0
  336. package/dist/webview/entry.js +182 -0
  337. package/dist/webview/render-activities.d.ts +24 -0
  338. package/dist/webview/render-activities.js +127 -0
  339. package/dist/webview/render-activity-card.d.ts +5 -0
  340. package/dist/webview/render-activity-card.js +168 -0
  341. package/dist/webview/render-applications.d.ts +11 -0
  342. package/dist/webview/render-applications.js +105 -0
  343. package/dist/webview/render-blocks.d.ts +5 -0
  344. package/dist/webview/render-blocks.js +71 -0
  345. package/dist/webview/render-capability-map.d.ts +7 -0
  346. package/dist/webview/render-capability-map.js +88 -0
  347. package/dist/webview/render-fgca.d.ts +8 -0
  348. package/dist/webview/render-fgca.js +94 -0
  349. package/dist/webview/render-goals.d.ts +6 -0
  350. package/dist/webview/render-goals.js +92 -0
  351. package/dist/webview/render-process-blueprint.d.ts +5 -0
  352. package/dist/webview/render-process-blueprint.js +135 -0
  353. package/dist/webview/render-process-map.d.ts +7 -0
  354. package/dist/webview/render-process-map.js +82 -0
  355. package/dist/webview/render-products.d.ts +7 -0
  356. package/dist/webview/render-products.js +81 -0
  357. package/dist/webview/render-scenarios.d.ts +7 -0
  358. package/dist/webview/render-scenarios.js +75 -0
  359. package/dist/webview/render-util.d.ts +16 -0
  360. package/dist/webview/render-util.js +31 -0
  361. package/dist/webview/transitrix-render.css +323 -0
  362. package/dist/webview/transitrix-render.js +6452 -0
  363. package/dist/webview/transitrix-render.js.map +7 -0
  364. package/dist/yaml-normalize.d.ts +20 -0
  365. package/dist/yaml-normalize.js +57 -0
  366. package/package.json +40 -0
  367. package/src/__tests__/edge-path.test.ts +46 -0
  368. package/src/__tests__/scope.test.ts +27 -0
  369. package/src/__tests__/yaml-normalize.test.ts +158 -0
  370. package/src/activities/__tests__/cpm.test.ts +164 -0
  371. package/src/activities/__tests__/gantt.test.ts +245 -0
  372. package/src/activities/__tests__/layout.test.ts +112 -0
  373. package/src/activities/__tests__/validate.test.ts +409 -0
  374. package/src/activities/cpm.ts +95 -0
  375. package/src/activities/gantt.ts +276 -0
  376. package/src/activities/index.ts +28 -0
  377. package/src/activities/layout.ts +112 -0
  378. package/src/activities/types.ts +148 -0
  379. package/src/activities/validate.ts +357 -0
  380. package/src/activity-card/__tests__/example.test.ts +66 -0
  381. package/src/activity-card/__tests__/layout.test.ts +63 -0
  382. package/src/activity-card/__tests__/resolver.test.ts +161 -0
  383. package/src/activity-card/__tests__/validate.test.ts +131 -0
  384. package/src/activity-card/index.ts +32 -0
  385. package/src/activity-card/layout.ts +194 -0
  386. package/src/activity-card/resolver.ts +337 -0
  387. package/src/activity-card/types.ts +237 -0
  388. package/src/activity-card/validate.ts +158 -0
  389. package/src/actor/__tests__/validate.test.ts +67 -0
  390. package/src/actor/index.ts +3 -0
  391. package/src/actor/types.ts +29 -0
  392. package/src/actor/validate.ts +38 -0
  393. package/src/applications/__tests__/validate.test.ts +256 -0
  394. package/src/applications/index.ts +11 -0
  395. package/src/applications/types.ts +43 -0
  396. package/src/applications/validate.ts +131 -0
  397. package/src/assertion/__tests__/example.test.ts +37 -0
  398. package/src/assertion/__tests__/validate.test.ts +142 -0
  399. package/src/assertion/index.ts +12 -0
  400. package/src/assertion/types.ts +58 -0
  401. package/src/assertion/validate.ts +145 -0
  402. package/src/blocks/__tests__/layout.test.ts +169 -0
  403. package/src/blocks/__tests__/validate.test.ts +260 -0
  404. package/src/blocks/index.ts +17 -0
  405. package/src/blocks/layout.ts +186 -0
  406. package/src/blocks/types.ts +62 -0
  407. package/src/blocks/validate.ts +206 -0
  408. package/src/capability-map/__tests__/validate.test.ts +238 -0
  409. package/src/capability-map/index.ts +8 -0
  410. package/src/capability-map/types.ts +29 -0
  411. package/src/capability-map/validate.ts +127 -0
  412. package/src/change/__tests__/validate.test.ts +88 -0
  413. package/src/change/index.ts +2 -0
  414. package/src/change/types.ts +23 -0
  415. package/src/change/validate.ts +61 -0
  416. package/src/compliance/__tests__/classify.test.ts +27 -0
  417. package/src/compliance/__tests__/confidence.test.ts +98 -0
  418. package/src/compliance/__tests__/coverage-metric.test.ts +188 -0
  419. package/src/compliance/__tests__/demo-scenario.test.ts +273 -0
  420. package/src/compliance/__tests__/example.test.ts +57 -0
  421. package/src/compliance/__tests__/gap-report.test.ts +129 -0
  422. package/src/compliance/__tests__/html.test.ts +142 -0
  423. package/src/compliance/__tests__/impact.test.ts +482 -0
  424. package/src/compliance/__tests__/markdown.test.ts +95 -0
  425. package/src/compliance/__tests__/views.test.ts +64 -0
  426. package/src/compliance/classify.ts +84 -0
  427. package/src/compliance/confidence.ts +69 -0
  428. package/src/compliance/coverage-metric.ts +263 -0
  429. package/src/compliance/gap-report.ts +94 -0
  430. package/src/compliance/html.ts +295 -0
  431. package/src/compliance/impact.ts +677 -0
  432. package/src/compliance/index.ts +56 -0
  433. package/src/compliance/markdown.ts +135 -0
  434. package/src/compliance/reverse-index.ts +33 -0
  435. package/src/compliance/types.ts +120 -0
  436. package/src/compliance/views.ts +46 -0
  437. package/src/compliance-matrix/__tests__/build.test.ts +200 -0
  438. package/src/compliance-matrix/__tests__/example.test.ts +57 -0
  439. package/src/compliance-matrix/build.ts +0 -0
  440. package/src/compliance-matrix/index.ts +11 -0
  441. package/src/compliance-matrix/types.ts +102 -0
  442. package/src/confidence/__tests__/score.test.ts +254 -0
  443. package/src/confidence/index.ts +21 -0
  444. package/src/confidence/score.ts +243 -0
  445. package/src/confidence/types.ts +96 -0
  446. package/src/edge-path.ts +44 -0
  447. package/src/element-envelope.ts +76 -0
  448. package/src/factor/__tests__/validate.test.ts +98 -0
  449. package/src/factor/index.ts +8 -0
  450. package/src/factor/types.ts +41 -0
  451. package/src/factor/validate.ts +74 -0
  452. package/src/fgca/__tests__/chain-table.test.ts +172 -0
  453. package/src/fgca/__tests__/layout.test.ts +179 -0
  454. package/src/fgca/__tests__/parse-canonical.test.ts +248 -0
  455. package/src/fgca/__tests__/preview-layout.test.ts +124 -0
  456. package/src/fgca/__tests__/validate.test.ts +142 -0
  457. package/src/fgca/chain-table.ts +225 -0
  458. package/src/fgca/index.ts +45 -0
  459. package/src/fgca/layout.ts +204 -0
  460. package/src/fgca/nodeTypes.ts +11 -0
  461. package/src/fgca/nodes/FGCAActivityNode.tsx +65 -0
  462. package/src/fgca/nodes/FGCAChangeNode.tsx +64 -0
  463. package/src/fgca/nodes/FGCAFactorNode.tsx +64 -0
  464. package/src/fgca/nodes/FGCAGoalNode.tsx +72 -0
  465. package/src/fgca/parse-canonical.ts +400 -0
  466. package/src/fgca/preview-layout.ts +217 -0
  467. package/src/fgca/types.ts +50 -0
  468. package/src/fgca/validate.ts +126 -0
  469. package/src/geometry.ts +9 -0
  470. package/src/goals/__tests__/layout.test.ts +175 -0
  471. package/src/goals/__tests__/mutations.test.ts +111 -0
  472. package/src/goals/__tests__/parse-canonical.test.ts +145 -0
  473. package/src/goals/__tests__/validate.test.ts +202 -0
  474. package/src/goals/index.ts +21 -0
  475. package/src/goals/layout.ts +172 -0
  476. package/src/goals/mutations.ts +123 -0
  477. package/src/goals/parse-canonical.ts +286 -0
  478. package/src/goals/types.ts +85 -0
  479. package/src/goals/validate.ts +136 -0
  480. package/src/index.ts +23 -0
  481. package/src/process-blueprint/__tests__/compliance-lane.test.ts +320 -0
  482. package/src/process-blueprint/__tests__/layout.test.ts +195 -0
  483. package/src/process-blueprint/__tests__/validate.test.ts +310 -0
  484. package/src/process-blueprint/index.ts +31 -0
  485. package/src/process-blueprint/layout.ts +531 -0
  486. package/src/process-blueprint/types.ts +242 -0
  487. package/src/process-blueprint/validate.ts +181 -0
  488. package/src/process-map/__tests__/validate.test.ts +223 -0
  489. package/src/process-map/index.ts +10 -0
  490. package/src/process-map/types.ts +36 -0
  491. package/src/process-map/validate.ts +135 -0
  492. package/src/products/__tests__/validate.test.ts +239 -0
  493. package/src/products/index.ts +9 -0
  494. package/src/products/types.ts +31 -0
  495. package/src/products/validate.ts +112 -0
  496. package/src/repo-validate/__tests__/validate-repo.test.ts +149 -0
  497. package/src/repo-validate/index.ts +2 -0
  498. package/src/repo-validate/types.ts +50 -0
  499. package/src/repo-validate/validate-repo.ts +196 -0
  500. package/src/requirement/__tests__/example.test.ts +24 -0
  501. package/src/requirement/__tests__/validate.test.ts +96 -0
  502. package/src/requirement/index.ts +4 -0
  503. package/src/requirement/types.ts +35 -0
  504. package/src/requirement/validate.ts +92 -0
  505. package/src/scenarios/__tests__/validate.test.ts +209 -0
  506. package/src/scenarios/index.ts +15 -0
  507. package/src/scenarios/types.ts +37 -0
  508. package/src/scenarios/validate.ts +122 -0
  509. package/src/schema-version.ts +16 -0
  510. package/src/scope.ts +46 -0
  511. package/src/stakeholder/__tests__/validate.test.ts +101 -0
  512. package/src/stakeholder/index.ts +3 -0
  513. package/src/stakeholder/types.ts +33 -0
  514. package/src/stakeholder/validate.ts +63 -0
  515. package/src/target-state/__tests__/validate.test.ts +74 -0
  516. package/src/target-state/index.ts +2 -0
  517. package/src/target-state/types.ts +28 -0
  518. package/src/target-state/validate.ts +67 -0
  519. package/src/theme/index.ts +4 -0
  520. package/src/theme/themes.ts +237 -0
  521. package/src/theme/tokens.ts +133 -0
  522. package/src/typed-id.ts +56 -0
  523. package/src/validation-types.ts +36 -0
  524. package/src/webview/__tests__/entry-notations.test.ts +77 -0
  525. package/src/webview/__tests__/entry.test.ts +64 -0
  526. package/src/webview/__tests__/render-goals.test.ts +72 -0
  527. package/src/webview/entry.ts +292 -0
  528. package/src/webview/render-activities.ts +156 -0
  529. package/src/webview/render-activity-card.ts +234 -0
  530. package/src/webview/render-applications.ts +123 -0
  531. package/src/webview/render-blocks.ts +91 -0
  532. package/src/webview/render-capability-map.ts +98 -0
  533. package/src/webview/render-fgca.ts +122 -0
  534. package/src/webview/render-goals.ts +110 -0
  535. package/src/webview/render-process-blueprint.ts +207 -0
  536. package/src/webview/render-process-map.ts +93 -0
  537. package/src/webview/render-products.ts +91 -0
  538. package/src/webview/render-scenarios.ts +90 -0
  539. package/src/webview/render-util.ts +34 -0
  540. package/src/webview/styles.css +323 -0
  541. package/src/yaml-normalize.ts +56 -0
@@ -0,0 +1,102 @@
1
+ // Compliance matrix model (vkgeorgia/strategy#84 Phase 2).
2
+ //
3
+ // The matrix is Products × Requirements. Each cell is the status of the
4
+ // (Product, Requirement) ASSERTION when one exists, or a gap when none does.
5
+ // Pure data — the layout is computed by `build.ts`; the Studio preview renders
6
+ // it. Non-product-subject assertions (CAPABILITY / PROCESS) belong to the
7
+ // single-product / single-law views (Phase 3) and are ignored here.
8
+
9
+ import type { AssertionStatus } from '../assertion/types.js';
10
+
11
+ /** A row of the matrix — a discovered PRODUCT element. */
12
+ export interface MatrixProduct {
13
+ id: string;
14
+ name: string;
15
+ /** True when the product was discovered only as an assertion subject (no
16
+ * product element file resolves it) — a dangling subject reference. */
17
+ unresolved?: boolean;
18
+ }
19
+
20
+ /** A column of the matrix — a discovered REQUIREMENT. */
21
+ export interface MatrixRequirement {
22
+ id: string;
23
+ name: string;
24
+ /** Organisation-defined priority (high | medium | low), used by the filter. */
25
+ severity?: string;
26
+ /** Typed IDs of the codex sources this requirement derives from (verbatim
27
+ * from `derived_from`). Carried so the renderer can surface provenance and
28
+ * resolve jurisdictions without re-scanning. */
29
+ derived_from?: string[];
30
+ /** Jurisdictions resolved by walking `derived_from` → codex artefacts and
31
+ * collecting their `jurisdiction` (Codex §1.1). Unique, sorted. Empty when
32
+ * no codex source resolves with a jurisdiction (internal-only requirements,
33
+ * or codex artefacts the scan did not find / has no jurisdiction). The
34
+ * toolbar jurisdiction filter (F16) keys on this set. */
35
+ jurisdictions?: string[];
36
+ }
37
+
38
+ /** A codex source document the matrix uses to resolve `Requirement.jurisdictions`. */
39
+ export interface MatrixCodexDoc {
40
+ id: string;
41
+ jurisdiction?: string;
42
+ }
43
+
44
+ /** The assertion fields the matrix needs (a projection of the full Assertion). */
45
+ export interface MatrixAssertionRef {
46
+ id: string;
47
+ about: string;
48
+ subject: string;
49
+ status: AssertionStatus;
50
+ assessed_at?: string;
51
+ next_review_at?: string;
52
+ }
53
+
54
+ /** One grid cell. `status === undefined` is a gap (no assertion for the pair). */
55
+ export interface MatrixCell {
56
+ productId: string;
57
+ requirementId: string;
58
+ /** The assertion filling this cell, when one exists. */
59
+ assertionId?: string;
60
+ status?: AssertionStatus;
61
+ assessed_at?: string;
62
+ next_review_at?: string;
63
+ }
64
+
65
+ export interface ComplianceMatrix {
66
+ /** Rows, sorted by id. */
67
+ products: MatrixProduct[];
68
+ /** Columns, sorted by id. */
69
+ requirements: MatrixRequirement[];
70
+ /** `cells[rowIndex][colIndex]`, aligned to `products` × `requirements`. */
71
+ cells: MatrixCell[][];
72
+ /** Counts for the summary strip. */
73
+ summary: {
74
+ products: number;
75
+ requirements: number;
76
+ assertions: number;
77
+ /** Cells with no assertion (compliance gaps). */
78
+ gaps: number;
79
+ };
80
+ }
81
+
82
+ export interface ComplianceMatrixInput {
83
+ products: MatrixProduct[];
84
+ requirements: MatrixRequirement[];
85
+ assertions: MatrixAssertionRef[];
86
+ /** Codex source documents — used to resolve each Requirement's jurisdictions
87
+ * by walking `derived_from`. Optional: when omitted the builder still works
88
+ * and every requirement's `jurisdictions` is left empty. */
89
+ codex?: MatrixCodexDoc[];
90
+ }
91
+
92
+ /** Toolbar filter — empty/omitted fields mean "no filter on that dimension". */
93
+ export interface MatrixFilter {
94
+ /** Keep only requirement columns whose severity is in this set. */
95
+ severities?: string[];
96
+ /** Keep only the cell status badges in this set; others render as filtered. */
97
+ statuses?: AssertionStatus[];
98
+ /** Keep only requirement columns whose resolved jurisdictions intersect this
99
+ * set. Requirements with no resolved jurisdiction are filtered out when this
100
+ * is non-empty (F16, epic #84). */
101
+ jurisdictions?: string[];
102
+ }
@@ -0,0 +1,254 @@
1
+ // Pins CONTRACT §11 — source-trust scale, freshness curve, element confidence,
2
+ // view composite, unsourced handling, manifest decay resolution, A–D bands.
3
+
4
+ import { describe, it, expect } from 'vitest';
5
+ import {
6
+ IMPLICIT_DECAY_DEFAULTS,
7
+ composeView,
8
+ confidenceBand,
9
+ formatConfidenceHeader,
10
+ freshness,
11
+ resolveDecayParams,
12
+ scoreElement,
13
+ scoreView,
14
+ sourceTrustWeight,
15
+ } from '../score.js';
16
+ import type { ConfidenceDecayConfig, DecayParams } from '../types.js';
17
+
18
+ const D: DecayParams = { fresh_days: 100, stale_days: 200, floor: 0.3 };
19
+
20
+ describe('sourceTrustWeight (§11.2)', () => {
21
+ it('matches the closed scale', () => {
22
+ expect(sourceTrustWeight('authoritative')).toBe(1.0);
23
+ expect(sourceTrustWeight('corroborated')).toBe(0.8);
24
+ expect(sourceTrustWeight('single_source')).toBe(0.5);
25
+ expect(sourceTrustWeight('unverified')).toBe(0.25);
26
+ });
27
+ });
28
+
29
+ describe('freshness (§11.3)', () => {
30
+ const today = '2026-06-05';
31
+
32
+ it('returns 1.0 when age ≤ fresh_days', () => {
33
+ expect(freshness('2026-05-01', today, D)).toBe(1.0);
34
+ expect(freshness('2026-06-05', today, D)).toBe(1.0);
35
+ });
36
+
37
+ it('returns floor when age ≥ stale_days', () => {
38
+ expect(freshness('2025-01-01', today, D)).toBeCloseTo(0.3, 10);
39
+ const exactlyStale = '2025-11-17';
40
+ expect(freshness(exactlyStale, today, { fresh_days: 100, stale_days: 200, floor: 0.3 })).toBeCloseTo(0.3, 10);
41
+ });
42
+
43
+ it('interpolates linearly between fresh_days and stale_days', () => {
44
+ // midpoint: age 150 → 1.0 − (1.0 − 0.3) · (150 − 100) / (200 − 100) = 0.65
45
+ const midAge = new Date(Date.UTC(2026, 5, 5));
46
+ midAge.setUTCDate(midAge.getUTCDate() - 150);
47
+ const iso = midAge.toISOString().slice(0, 10);
48
+ expect(freshness(iso, today, D)).toBeCloseTo(0.65, 10);
49
+ });
50
+
51
+ it('clamps future-dated admitted_at to 1.0', () => {
52
+ expect(freshness('2030-01-01', today, D)).toBe(1.0);
53
+ });
54
+
55
+ it('treats absent admitted_at as fully stale (returns floor)', () => {
56
+ expect(freshness(undefined, today, D)).toBe(0.3);
57
+ });
58
+
59
+ it('treats unparseable admitted_at as fully stale', () => {
60
+ expect(freshness('not-a-date', today, D)).toBe(0.3);
61
+ expect(freshness('2026/06/05', today, D)).toBe(0.3);
62
+ expect(freshness('2026-13-01', today, D)).toBe(0.3);
63
+ });
64
+
65
+ it('returns floor when stale_days <= fresh_days (degenerate config) and age exceeds fresh', () => {
66
+ // age 365d for 2025-06-05; both branches collapse to floor.
67
+ expect(freshness('2025-06-05', today, { fresh_days: 100, stale_days: 100, floor: 0.4 })).toBe(0.4);
68
+ expect(freshness('2025-06-05', today, { fresh_days: 200, stale_days: 100, floor: 0.4 })).toBe(0.4);
69
+ });
70
+ });
71
+
72
+ describe('resolveDecayParams (§11.3 + MANIFEST §2)', () => {
73
+ const config: ConfidenceDecayConfig = {
74
+ defaults: { fresh_days: 180, stale_days: 730, floor: 0.3 },
75
+ by_type: {
76
+ CAPABILITY: { fresh_days: 365, stale_days: 1825 },
77
+ APPLICATION: { fresh_days: 180, stale_days: 730 },
78
+ },
79
+ };
80
+
81
+ it('uses by_type overrides and inherits missing fields from defaults', () => {
82
+ // Pins the §11.3 example: CAPABILITY overrides fresh/stale but inherits floor=0.3.
83
+ expect(resolveDecayParams('CAPABILITY', config)).toEqual({ fresh_days: 365, stale_days: 1825, floor: 0.3 });
84
+ });
85
+
86
+ it('falls back to defaults for an unknown TYPE', () => {
87
+ expect(resolveDecayParams('PROCESS', config)).toEqual({ fresh_days: 180, stale_days: 730, floor: 0.3 });
88
+ });
89
+
90
+ it('falls back to §11.3 implicit defaults when the whole block is omitted', () => {
91
+ expect(resolveDecayParams('CAPABILITY', undefined)).toEqual(IMPLICIT_DECAY_DEFAULTS);
92
+ expect(resolveDecayParams('CAPABILITY', {})).toEqual(IMPLICIT_DECAY_DEFAULTS);
93
+ });
94
+
95
+ it('fills missing defaults fields from §11.3 implicit defaults', () => {
96
+ const partial: ConfidenceDecayConfig = { defaults: { floor: 0.5 } };
97
+ expect(resolveDecayParams('PROCESS', partial)).toEqual({
98
+ fresh_days: IMPLICIT_DECAY_DEFAULTS.fresh_days,
99
+ stale_days: IMPLICIT_DECAY_DEFAULTS.stale_days,
100
+ floor: 0.5,
101
+ });
102
+ });
103
+ });
104
+
105
+ describe('scoreElement (§11.4–11.5)', () => {
106
+ const today = '2026-06-05';
107
+
108
+ it('takes the max source-trust weight (§11.4): extra weak sources never subtract', () => {
109
+ const score = scoreElement(
110
+ { type: 'CAPABILITY', admitted_at: today, sources: ['unverified', 'authoritative', 'single_source'] },
111
+ today,
112
+ );
113
+ expect(score.source_trust).toBe(1.0);
114
+ expect(score.confidence).toBe(1.0);
115
+ expect(score.sourced).toBe(true);
116
+ });
117
+
118
+ it('multiplies source_trust by freshness', () => {
119
+ // §11.4 worked example: authoritative · floor.
120
+ const score = scoreElement(
121
+ { type: 'X', admitted_at: '2024-01-01', sources: ['authoritative'] },
122
+ today,
123
+ { defaults: D },
124
+ );
125
+ expect(score.source_trust).toBe(1.0);
126
+ expect(score.freshness).toBeCloseTo(0.3, 10);
127
+ expect(score.confidence).toBeCloseTo(0.3, 10);
128
+ });
129
+
130
+ it('marks unsourced elements at the unverified floor (§11.5) and sourced=false', () => {
131
+ const undef = scoreElement({ type: 'X', admitted_at: today }, today);
132
+ const empty = scoreElement({ type: 'X', admitted_at: today, sources: [] }, today);
133
+ for (const score of [undef, empty]) {
134
+ expect(score.source_trust).toBe(0.25);
135
+ expect(score.sourced).toBe(false);
136
+ expect(score.confidence).toBeCloseTo(0.25, 10);
137
+ }
138
+ });
139
+
140
+ it('distinguishes unsourced (counted separately) from sourced-but-unverified', () => {
141
+ const unsourced = scoreElement({ type: 'X', admitted_at: today }, today);
142
+ const sourcedUnverified = scoreElement(
143
+ { type: 'X', admitted_at: today, sources: ['unverified'] },
144
+ today,
145
+ );
146
+ expect(unsourced.source_trust).toBe(sourcedUnverified.source_trust);
147
+ expect(unsourced.sourced).toBe(false);
148
+ expect(sourcedUnverified.sourced).toBe(true);
149
+ });
150
+ });
151
+
152
+ describe('confidenceBand (§11.6)', () => {
153
+ it('uses the documented thresholds', () => {
154
+ expect(confidenceBand(1.0)).toBe('A');
155
+ expect(confidenceBand(0.8)).toBe('A');
156
+ expect(confidenceBand(0.79)).toBe('B');
157
+ expect(confidenceBand(0.6)).toBe('B');
158
+ expect(confidenceBand(0.59)).toBe('C');
159
+ expect(confidenceBand(0.4)).toBe('C');
160
+ expect(confidenceBand(0.39)).toBe('D');
161
+ expect(confidenceBand(0)).toBe('D');
162
+ });
163
+ });
164
+
165
+ describe('composeView (§11.6)', () => {
166
+ it('headlines weakest link, reports mean and coverage', () => {
167
+ const composite = composeView([
168
+ { source_trust: 1.0, freshness: 1.0, confidence: 1.0, sourced: true },
169
+ { source_trust: 0.5, freshness: 0.8, confidence: 0.4, sourced: true },
170
+ { source_trust: 0.25, freshness: 1.0, confidence: 0.25, sourced: false },
171
+ ]);
172
+ expect(composite.weakest_link).toBeCloseTo(0.25, 10);
173
+ expect(composite.band).toBe('D');
174
+ expect(composite.mean).toBeCloseTo((1.0 + 0.4 + 0.25) / 3, 10);
175
+ expect(composite.coverage).toBeCloseTo(2 / 3, 10);
176
+ expect(composite.element_count).toBe(3);
177
+ expect(composite.unsourced_count).toBe(1);
178
+ });
179
+
180
+ it('returns a degenerate composite (band D, counts 0) for an empty set', () => {
181
+ const composite = composeView([]);
182
+ expect(composite.element_count).toBe(0);
183
+ expect(composite.weakest_link).toBe(0);
184
+ expect(composite.mean).toBe(0);
185
+ expect(composite.coverage).toBe(0);
186
+ expect(composite.band).toBe('D');
187
+ });
188
+ });
189
+
190
+ describe('scoreView end-to-end', () => {
191
+ const today = '2026-06-05';
192
+
193
+ it('composes weakest-link / mean / coverage / band from heterogenous elements', () => {
194
+ const view = scoreView(
195
+ [
196
+ // weakest link = 0.50 → band C
197
+ { type: 'X', admitted_at: today, sources: ['authoritative'] }, // 1.00
198
+ { type: 'X', admitted_at: today, sources: ['corroborated'] }, // 0.80
199
+ { type: 'X', admitted_at: today, sources: ['single_source'] }, // 0.50
200
+ { type: 'X', admitted_at: today, sources: ['corroborated'] }, // 0.80
201
+ ],
202
+ today,
203
+ );
204
+ expect(view.composite.weakest_link).toBeCloseTo(0.5, 10);
205
+ expect(view.composite.band).toBe('C');
206
+ expect(view.composite.mean).toBeCloseTo((1.0 + 0.8 + 0.5 + 0.8) / 4, 10);
207
+ expect(view.composite.coverage).toBe(1);
208
+ expect(view.composite.unsourced_count).toBe(0);
209
+ expect(view.today).toBe(today);
210
+ });
211
+
212
+ it('accepts Date for today and converts to ISO', () => {
213
+ const view = scoreView(
214
+ [{ type: 'X', admitted_at: '2026-06-05', sources: ['authoritative'] }],
215
+ new Date(Date.UTC(2026, 5, 5)),
216
+ );
217
+ expect(view.today).toBe('2026-06-05');
218
+ expect(view.composite.band).toBe('A');
219
+ });
220
+
221
+ it('reports band D and 0% coverage when every element is unsourced and fully stale', () => {
222
+ const view = scoreView(
223
+ [
224
+ { type: 'X' },
225
+ { type: 'X' },
226
+ ],
227
+ '2026-06-05',
228
+ { defaults: D },
229
+ );
230
+ // unsourced (0.25) · freshness floor (0.3) = 0.075 → band D.
231
+ expect(view.composite.coverage).toBe(0);
232
+ expect(view.composite.unsourced_count).toBe(2);
233
+ expect(view.composite.band).toBe('D');
234
+ expect(view.composite.weakest_link).toBeCloseTo(0.075, 10);
235
+ });
236
+ });
237
+
238
+ describe('formatConfidenceHeader (§11.6)', () => {
239
+ it('renders the documented header shape', () => {
240
+ const view = scoreView(
241
+ [{ type: 'X', admitted_at: '2026-06-05', sources: ['corroborated'] }],
242
+ '2026-06-05',
243
+ );
244
+ // corroborated (0.8) · freshness 1.0 = 0.80 → band A (A is ≥ 0.8).
245
+ expect(formatConfidenceHeader(view)).toBe(
246
+ 'Data confidence (as of 2026-06-05): A (weakest link) · 0.80 mean · 100% sourced',
247
+ );
248
+ });
249
+
250
+ it('returns an empty string for an empty composite', () => {
251
+ const view = scoreView([], '2026-06-05');
252
+ expect(formatConfidenceHeader(view)).toBe('');
253
+ });
254
+ });
@@ -0,0 +1,21 @@
1
+ export {
2
+ IMPLICIT_DECAY_DEFAULTS,
3
+ composeView,
4
+ confidenceBand,
5
+ formatConfidenceHeader,
6
+ freshness,
7
+ resolveDecayParams,
8
+ scoreElement,
9
+ scoreView,
10
+ sourceTrustWeight,
11
+ } from './score.js';
12
+ export type {
13
+ ConfidenceBand,
14
+ ConfidenceDecayConfig,
15
+ DecayParams,
16
+ ElementScore,
17
+ ScoringElement,
18
+ SourceQuality,
19
+ ViewComposite,
20
+ ViewScore,
21
+ } from './types.js';
@@ -0,0 +1,243 @@
1
+ // Pure confidence-scoring per methodology CONTRACT §11. No I/O, no mutation:
2
+ // callers (Studio previews, DSM React) pass already-resolved element inputs
3
+ // and a `today` reference; this module returns the per-element scores and
4
+ // the view composite. Lives in @transitrix/diagrams so Studio and DSM share
5
+ // one implementation (see vkgeorgia/strategy#159 — DQ-1).
6
+
7
+ import type {
8
+ ConfidenceBand,
9
+ ConfidenceDecayConfig,
10
+ DecayParams,
11
+ ElementScore,
12
+ ScoringElement,
13
+ SourceQuality,
14
+ ViewComposite,
15
+ ViewScore,
16
+ } from './types.js';
17
+
18
+ /** §11.3 implicit defaults — used when the adopter omits `confidence_decay`. */
19
+ export const IMPLICIT_DECAY_DEFAULTS: DecayParams = {
20
+ fresh_days: 180,
21
+ stale_days: 730,
22
+ floor: 0.3,
23
+ };
24
+
25
+ /** §11.2 source-trust weights. The fallback for an unknown label is `unverified`. */
26
+ const SOURCE_TRUST_WEIGHTS: Record<SourceQuality, number> = {
27
+ authoritative: 1.0,
28
+ corroborated: 0.8,
29
+ single_source: 0.5,
30
+ unverified: 0.25,
31
+ };
32
+
33
+ /** Weight for the `unverified` floor — both an explicit label and the unsourced fallback. */
34
+ const UNVERIFIED_WEIGHT = SOURCE_TRUST_WEIGHTS.unverified;
35
+
36
+ export function sourceTrustWeight(q: SourceQuality): number {
37
+ return SOURCE_TRUST_WEIGHTS[q] ?? UNVERIFIED_WEIGHT;
38
+ }
39
+
40
+ /**
41
+ * Resolves the decay parameters for one element TYPE: `by_type[type]` overrides
42
+ * `defaults`, which overrides {@link IMPLICIT_DECAY_DEFAULTS}. Each field
43
+ * resolves independently — a `by_type` entry that sets only `fresh_days` /
44
+ * `stale_days` inherits `floor` from `defaults` (per the §11.3 example).
45
+ */
46
+ export function resolveDecayParams(
47
+ type: string,
48
+ config?: ConfidenceDecayConfig,
49
+ ): DecayParams {
50
+ const typeOverride = config?.by_type?.[type] ?? {};
51
+ const defaults = config?.defaults ?? {};
52
+ return {
53
+ fresh_days: pick(typeOverride.fresh_days, defaults.fresh_days, IMPLICIT_DECAY_DEFAULTS.fresh_days),
54
+ stale_days: pick(typeOverride.stale_days, defaults.stale_days, IMPLICIT_DECAY_DEFAULTS.stale_days),
55
+ floor: pick(typeOverride.floor, defaults.floor, IMPLICIT_DECAY_DEFAULTS.floor),
56
+ };
57
+ }
58
+
59
+ function pick(...values: (number | undefined)[]): number {
60
+ for (const v of values) if (typeof v === 'number' && Number.isFinite(v)) return v;
61
+ // The trailing implicit default is always finite; this is only reached if a
62
+ // caller passes a non-finite implicit default — which we treat as 0.
63
+ return 0;
64
+ }
65
+
66
+ /**
67
+ * Computes freshness per CONTRACT §11.3.
68
+ *
69
+ * ```
70
+ * freshness = 1.0 if age_days ≤ fresh_days
71
+ * = floor if age_days ≥ stale_days
72
+ * = 1.0 − (1.0 − floor) · (age_days − fresh_days) / (stale_days − fresh_days) otherwise
73
+ * ```
74
+ *
75
+ * Future-dated `admitted_at` (negative age) is clamped to 1.0. An absent or
76
+ * unparseable `admitted_at` returns `floor` — the element is treated as fully
77
+ * stale, never as fresh.
78
+ */
79
+ export function freshness(
80
+ admittedAt: string | undefined,
81
+ today: string,
82
+ params: DecayParams,
83
+ ): number {
84
+ if (!admittedAt) return params.floor;
85
+ const age = daysBetween(admittedAt, today);
86
+ if (age === null) return params.floor;
87
+ if (age <= params.fresh_days) return 1.0;
88
+ if (age >= params.stale_days) return params.floor;
89
+ // Linear interpolation. Guard against a degenerate fresh==stale config: the
90
+ // two equalities above would already have returned, but if a caller passes
91
+ // stale_days < fresh_days we keep monotonic behaviour by returning floor.
92
+ const span = params.stale_days - params.fresh_days;
93
+ if (span <= 0) return params.floor;
94
+ return 1.0 - (1.0 - params.floor) * ((age - params.fresh_days) / span);
95
+ }
96
+
97
+ /**
98
+ * Scores one element per §11.4–11.5. The element's `sources` list is the
99
+ * resolved `derived_from` provenance: max trust wins (`max` in §11.4), and
100
+ * an empty / absent list marks the element unsourced — scored at the
101
+ * `unverified` floor and `sourced: false` so the view composite can count
102
+ * it separately per §11.5.
103
+ */
104
+ export function scoreElement(
105
+ element: ScoringElement,
106
+ today: string,
107
+ config?: ConfidenceDecayConfig,
108
+ ): ElementScore {
109
+ const sources = element.sources ?? [];
110
+ const sourced = sources.length > 0;
111
+ const source_trust = sourced
112
+ ? Math.max(...sources.map(sourceTrustWeight))
113
+ : UNVERIFIED_WEIGHT;
114
+ const params = resolveDecayParams(element.type, config);
115
+ const f = freshness(element.admitted_at, today, params);
116
+ return {
117
+ source_trust,
118
+ freshness: f,
119
+ confidence: source_trust * f,
120
+ sourced,
121
+ };
122
+ }
123
+
124
+ /** Confidence band per §11.6: A ≥ 0.8, B ≥ 0.6, C ≥ 0.4, D < 0.4. */
125
+ export function confidenceBand(confidence: number): ConfidenceBand {
126
+ if (confidence >= 0.8) return 'A';
127
+ if (confidence >= 0.6) return 'B';
128
+ if (confidence >= 0.4) return 'C';
129
+ return 'D';
130
+ }
131
+
132
+ /**
133
+ * Rolls per-element scores up to the view composite per §11.6. The weakest
134
+ * link is the headline; mean and coverage are reported alongside. An empty
135
+ * element set returns a degenerate composite (`weakest_link` and `mean` of 0,
136
+ * coverage 0, band `D`) — callers should detect `element_count === 0` if they
137
+ * want to suppress the header entirely rather than display the floor.
138
+ */
139
+ export function composeView(scores: ElementScore[]): ViewComposite {
140
+ if (scores.length === 0) {
141
+ return {
142
+ weakest_link: 0,
143
+ mean: 0,
144
+ coverage: 0,
145
+ band: 'D',
146
+ element_count: 0,
147
+ unsourced_count: 0,
148
+ };
149
+ }
150
+ let min = Infinity;
151
+ let sum = 0;
152
+ let sourcedCount = 0;
153
+ for (const s of scores) {
154
+ if (s.confidence < min) min = s.confidence;
155
+ sum += s.confidence;
156
+ if (s.sourced) sourcedCount += 1;
157
+ }
158
+ const mean = sum / scores.length;
159
+ return {
160
+ weakest_link: min,
161
+ mean,
162
+ coverage: sourcedCount / scores.length,
163
+ band: confidenceBand(min),
164
+ element_count: scores.length,
165
+ unsourced_count: scores.length - sourcedCount,
166
+ };
167
+ }
168
+
169
+ /**
170
+ * Scores a view's elements end-to-end. The caller passes the rendered set,
171
+ * the adopter manifest's `confidence_decay` block (optional), and the
172
+ * reference date (`today`); the result is purely derived and never stored
173
+ * — re-running on the same inputs yields the same numbers (§11.6).
174
+ */
175
+ export function scoreView(
176
+ elements: ScoringElement[],
177
+ today: string | Date,
178
+ config?: ConfidenceDecayConfig,
179
+ ): ViewScore {
180
+ const todayIso = toIsoDate(today);
181
+ const elementScores = elements.map(e => scoreElement(e, todayIso, config));
182
+ return {
183
+ elements: elementScores,
184
+ composite: composeView(elementScores),
185
+ today: todayIso,
186
+ };
187
+ }
188
+
189
+ /**
190
+ * Renders the §11.6 example header line, e.g.:
191
+ *
192
+ * ```
193
+ * Data confidence (as of 2026-06-05): B (weakest link) · 0.71 mean · 92% sourced
194
+ * ```
195
+ *
196
+ * Shared between Studio (DQ-2) and DSM (DQ-5) so the banding/labels stay
197
+ * consistent across the two consumers. Returns an empty string for an empty
198
+ * composite — callers decide whether to suppress the header in that case.
199
+ */
200
+ export function formatConfidenceHeader(view: ViewScore): string {
201
+ const c = view.composite;
202
+ if (c.element_count === 0) return '';
203
+ const mean = c.mean.toFixed(2);
204
+ const coverage = Math.round(c.coverage * 100);
205
+ return `Data confidence (as of ${view.today}): ${c.band} (weakest link) · ${mean} mean · ${coverage}% sourced`;
206
+ }
207
+
208
+ // ── Date helpers ────────────────────────────────────────────────────────────
209
+
210
+ /** Strict ISO-date parser. Accepts `YYYY-MM-DD`; returns null on anything else. */
211
+ const ISO_DATE_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
212
+
213
+ function parseIsoDate(s: string): Date | null {
214
+ const m = ISO_DATE_RE.exec(s);
215
+ if (!m) return null;
216
+ const year = Number(m[1]);
217
+ const month = Number(m[2]);
218
+ const day = Number(m[3]);
219
+ // UTC to avoid the host TZ shifting age_days by ±1.
220
+ const d = new Date(Date.UTC(year, month - 1, day));
221
+ if (
222
+ d.getUTCFullYear() !== year ||
223
+ d.getUTCMonth() !== month - 1 ||
224
+ d.getUTCDate() !== day
225
+ ) return null;
226
+ return d;
227
+ }
228
+
229
+ function toIsoDate(d: string | Date): string {
230
+ if (typeof d === 'string') return d;
231
+ const y = d.getUTCFullYear();
232
+ const m = String(d.getUTCMonth() + 1).padStart(2, '0');
233
+ const day = String(d.getUTCDate()).padStart(2, '0');
234
+ return `${y}-${m}-${day}`;
235
+ }
236
+
237
+ function daysBetween(fromIso: string, toIso: string): number | null {
238
+ const from = parseIsoDate(fromIso);
239
+ const to = parseIsoDate(toIso);
240
+ if (!from || !to) return null;
241
+ const ms = to.getTime() - from.getTime();
242
+ return Math.floor(ms / (24 * 60 * 60 * 1000));
243
+ }