@transitrix/diagrams 1.8.20 → 1.9.18

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 (176) hide show
  1. package/dist/activity-card/types.js +1 -1
  2. package/dist/agreement.d.ts +15 -0
  3. package/dist/agreement.js +61 -0
  4. package/dist/applications/index.d.ts +2 -0
  5. package/dist/applications/index.js +1 -0
  6. package/dist/applications/resolve-maturity.d.ts +16 -0
  7. package/dist/applications/resolve-maturity.js +57 -0
  8. package/dist/applications/validate.js +20 -0
  9. package/dist/capability-map/CapabilityCardNode.d.ts +1 -1
  10. package/dist/capability-map/CapabilityRootNode.d.ts +1 -1
  11. package/dist/capability-map/index.d.ts +2 -0
  12. package/dist/capability-map/index.js +1 -0
  13. package/dist/capability-map/resolve-maturity.d.ts +20 -0
  14. package/dist/capability-map/resolve-maturity.js +72 -0
  15. package/dist/capability-map/validate.js +1 -2
  16. package/dist/compliance/classify.d.ts +10 -1
  17. package/dist/compliance/classify.js +44 -2
  18. package/dist/compliance/confidence.js +1 -1
  19. package/dist/compliance/gap-report.d.ts +31 -1
  20. package/dist/compliance/gap-report.js +55 -3
  21. package/dist/compliance/html.d.ts +1 -1
  22. package/dist/compliance/html.js +15 -7
  23. package/dist/compliance/impact.js +1 -1
  24. package/dist/compliance/index.d.ts +3 -1
  25. package/dist/compliance/index.js +2 -0
  26. package/dist/compliance/markdown.js +26 -4
  27. package/dist/compliance/reverse-index.d.ts +6 -1
  28. package/dist/compliance/reverse-index.js +27 -3
  29. package/dist/compliance/trace.js +5 -1
  30. package/dist/compliance/types.d.ts +70 -0
  31. package/dist/compliance/types.js +1 -2
  32. package/dist/compliance/views.js +1 -1
  33. package/dist/compliance-matrix/types.d.ts +1 -1
  34. package/dist/compliance-matrix/types.js +1 -1
  35. package/dist/confidence/score.js +1 -1
  36. package/dist/constraint/validate.js +3 -0
  37. package/dist/edge-path.js +1 -1
  38. package/dist/factor/validate.js +2 -2
  39. package/dist/fgca/chain-table.js +1 -1
  40. package/dist/fgca/nodeTypes.d.ts +4 -4
  41. package/dist/fgca/nodes/FGCAActivityNode.d.ts +1 -1
  42. package/dist/fgca/nodes/FGCAChangeNode.d.ts +1 -1
  43. package/dist/fgca/nodes/FGCAFactorNode.d.ts +1 -1
  44. package/dist/fgca/nodes/FGCAGoalNode.d.ts +1 -1
  45. package/dist/fgca/parse-canonical.d.ts +2 -2
  46. package/dist/fgca/parse-canonical.js +2 -2
  47. package/dist/fgca/preview-layout.d.ts +1 -1
  48. package/dist/goals/GoalNode.d.ts +1 -1
  49. package/dist/goals/layout.d.ts +1 -1
  50. package/dist/goals/layout.js +1 -1
  51. package/dist/goals/parse-canonical.d.ts +1 -1
  52. package/dist/goals/parse-canonical.js +1 -1
  53. package/dist/goals/types.d.ts +1 -1
  54. package/dist/goals/validate.js +2 -2
  55. package/dist/link-suspicion.d.ts +43 -0
  56. package/dist/link-suspicion.js +156 -0
  57. package/dist/metric/index.d.ts +4 -0
  58. package/dist/metric/index.js +2 -0
  59. package/dist/metric/types.d.ts +24 -0
  60. package/dist/metric/types.js +7 -0
  61. package/dist/metric/validate.d.ts +7 -0
  62. package/dist/metric/validate.js +90 -0
  63. package/dist/need/index.d.ts +3 -0
  64. package/dist/need/index.js +1 -0
  65. package/dist/need/types.d.ts +21 -0
  66. package/dist/need/types.js +3 -0
  67. package/dist/need/validate.d.ts +7 -0
  68. package/dist/need/validate.js +60 -0
  69. package/dist/repo-validate/check-versioned-attributes.d.ts +4 -0
  70. package/dist/repo-validate/check-versioned-attributes.js +105 -0
  71. package/dist/repo-validate/validate-repo.js +3 -0
  72. package/dist/requirement/index.d.ts +2 -2
  73. package/dist/requirement/index.js +1 -1
  74. package/dist/requirement/types.d.ts +18 -0
  75. package/dist/requirement/types.js +2 -0
  76. package/dist/requirement/validate.js +22 -1
  77. package/dist/risk/index.d.ts +4 -0
  78. package/dist/risk/index.js +2 -0
  79. package/dist/risk/types.d.ts +25 -0
  80. package/dist/risk/types.js +3 -0
  81. package/dist/risk/validate.d.ts +7 -0
  82. package/dist/risk/validate.js +108 -0
  83. package/dist/schema-version.d.ts +9 -6
  84. package/dist/schema-version.js +9 -6
  85. package/dist/theme/themes.js +3 -2
  86. package/dist/validation/index.d.ts +4 -0
  87. package/dist/validation/index.js +2 -0
  88. package/dist/validation/types.d.ts +31 -0
  89. package/dist/validation/types.js +21 -0
  90. package/dist/validation/validate.d.ts +7 -0
  91. package/dist/validation/validate.js +106 -0
  92. package/dist/verification/index.d.ts +4 -0
  93. package/dist/verification/index.js +2 -0
  94. package/dist/verification/types.d.ts +31 -0
  95. package/dist/verification/types.js +12 -0
  96. package/dist/verification/validate.d.ts +7 -0
  97. package/dist/verification/validate.js +104 -0
  98. package/dist/versioned-attribute/index.d.ts +4 -0
  99. package/dist/versioned-attribute/index.js +2 -0
  100. package/dist/versioned-attribute/resolve.d.ts +15 -0
  101. package/dist/versioned-attribute/resolve.js +37 -0
  102. package/dist/versioned-attribute/types.d.ts +12 -0
  103. package/dist/versioned-attribute/types.js +6 -0
  104. package/dist/versioned-attribute/validate.d.ts +17 -0
  105. package/dist/versioned-attribute/validate.js +87 -0
  106. package/dist/webview/render-applications.d.ts +15 -1
  107. package/dist/webview/render-applications.js +7 -2
  108. package/dist/webview/render-blocks.js +6 -4
  109. package/dist/webview/render-capability-map.d.ts +16 -1
  110. package/dist/webview/render-capability-map.js +4 -1
  111. package/dist/webview/render-process.d.ts +1 -1
  112. package/dist/webview/render-process.js +5 -5
  113. package/package.json +9 -6
  114. package/src/activity-card/types.ts +1 -1
  115. package/src/agreement.ts +71 -0
  116. package/src/applications/index.ts +3 -0
  117. package/src/applications/resolve-maturity.ts +66 -0
  118. package/src/applications/validate.ts +23 -0
  119. package/src/capability-map/index.ts +7 -0
  120. package/src/capability-map/resolve-maturity.ts +84 -0
  121. package/src/capability-map/validate.ts +1 -2
  122. package/src/compliance/classify.ts +54 -3
  123. package/src/compliance/confidence.ts +1 -1
  124. package/src/compliance/gap-report.ts +91 -4
  125. package/src/compliance/html.ts +19 -7
  126. package/src/compliance/impact.ts +1 -1
  127. package/src/compliance/index.ts +6 -0
  128. package/src/compliance/markdown.ts +18 -4
  129. package/src/compliance/reverse-index.ts +35 -4
  130. package/src/compliance/trace.ts +7 -1
  131. package/src/compliance/types.ts +75 -2
  132. package/src/compliance/views.ts +1 -1
  133. package/src/compliance-matrix/types.ts +2 -2
  134. package/src/confidence/score.ts +1 -1
  135. package/src/constraint/validate.ts +4 -0
  136. package/src/edge-path.ts +1 -1
  137. package/src/factor/validate.ts +2 -2
  138. package/src/fgca/chain-table.ts +1 -1
  139. package/src/fgca/parse-canonical.ts +2 -2
  140. package/src/fgca/preview-layout.ts +1 -1
  141. package/src/goals/layout.ts +1 -1
  142. package/src/goals/parse-canonical.ts +1 -1
  143. package/src/goals/types.ts +1 -1
  144. package/src/goals/validate.ts +2 -2
  145. package/src/link-suspicion.ts +181 -0
  146. package/src/metric/index.ts +4 -0
  147. package/src/metric/types.ts +38 -0
  148. package/src/metric/validate.ts +102 -0
  149. package/src/need/index.ts +3 -0
  150. package/src/need/types.ts +31 -0
  151. package/src/need/validate.ts +71 -0
  152. package/src/repo-validate/check-versioned-attributes.ts +112 -0
  153. package/src/repo-validate/validate-repo.ts +3 -0
  154. package/src/requirement/index.ts +2 -2
  155. package/src/requirement/types.ts +25 -0
  156. package/src/requirement/validate.ts +25 -1
  157. package/src/risk/index.ts +4 -0
  158. package/src/risk/types.ts +35 -0
  159. package/src/risk/validate.ts +121 -0
  160. package/src/schema-version.ts +9 -6
  161. package/src/theme/themes.ts +3 -2
  162. package/src/validation/index.ts +4 -0
  163. package/src/validation/types.ts +57 -0
  164. package/src/validation/validate.ts +113 -0
  165. package/src/verification/index.ts +4 -0
  166. package/src/verification/types.ts +48 -0
  167. package/src/verification/validate.ts +111 -0
  168. package/src/versioned-attribute/index.ts +4 -0
  169. package/src/versioned-attribute/resolve.ts +45 -0
  170. package/src/versioned-attribute/types.ts +19 -0
  171. package/src/versioned-attribute/validate.ts +109 -0
  172. package/src/webview/render-applications.ts +25 -2
  173. package/src/webview/render-blocks.ts +8 -4
  174. package/src/webview/render-capability-map.ts +23 -1
  175. package/src/webview/render-process.ts +5 -5
  176. package/src/webview/styles.css +6 -0
@@ -1,10 +1,12 @@
1
- // Canon-artefact classification (vkgeorgia/strategy#84). The single authority
1
+ // Canon-artefact classification. The single authority
2
2
  // for "what is a compliance artefact" — used by both the Studio extension scan
3
3
  // (webview previews) and the CLI scan (`export-compliance`), so the recognition
4
4
  // rules live once. Pure: takes a parsed YAML document, no IO.
5
5
 
6
6
  import type { AssertionStatus } from '../assertion/types.js';
7
- import type { IndexAssertion, IndexRequirement } from './types.js';
7
+ import type { VerificationMethod, VerificationOutcome } from '../verification/types.js';
8
+ import type { ValidationMethod, ValidationOutcome } from '../validation/types.js';
9
+ import type { IndexAssertion, IndexRequirement, IndexVerification, IndexNeed, IndexValidation } from './types.js';
8
10
 
9
11
  export interface ComplianceProduct {
10
12
  id: string;
@@ -22,6 +24,8 @@ export interface ComplianceCanon {
22
24
  products: ComplianceProduct[];
23
25
  requirements: IndexRequirement[];
24
26
  assertions: IndexAssertion[];
27
+ /** VERIFICATION artefacts (27-verification.md) — the engineering V&V peer of `assertions`. */
28
+ verifications: IndexVerification[];
25
29
  codex: ComplianceCodexDoc[];
26
30
  /**
27
31
  * Named elements that can serve as assertion subjects beyond `products`:
@@ -29,10 +33,26 @@ export interface ComplianceCanon {
29
33
  * `ingestComplianceDoc` for the corresponding notation values.
30
34
  */
31
35
  subjects: ComplianceProduct[];
36
+ /** NEED elements (ELEMENT_PRIMITIVES.md §7.28) — upstream of `requirements`.
37
+ * Ingested so NEED-COVERAGE-001 / NEED-VALIDATION-COVERAGE-001..002 can
38
+ * compute coverage over the full needs catalogue. */
39
+ needs: IndexNeed[];
40
+ /** VALIDATION artefacts (28-validation.md) — the validation-domain peer of
41
+ * `verifications`, anchored on NEED instead of REQUIREMENT. */
42
+ validations: IndexValidation[];
32
43
  }
33
44
 
34
45
  export function emptyCanon(): ComplianceCanon {
35
- return { products: [], requirements: [], assertions: [], codex: [], subjects: [] };
46
+ return {
47
+ products: [],
48
+ requirements: [],
49
+ assertions: [],
50
+ verifications: [],
51
+ codex: [],
52
+ subjects: [],
53
+ needs: [],
54
+ validations: [],
55
+ };
36
56
  }
37
57
 
38
58
  const str = (v: unknown): string | undefined => (typeof v === 'string' ? v : undefined);
@@ -79,6 +99,24 @@ export function ingestComplianceDoc(canon: ComplianceCanon, doc: unknown): strin
79
99
  element_kind: d.notation,
80
100
  description: str(d.description),
81
101
  next_review_at: str(d.next_review_at),
102
+ serves: str(d.serves),
103
+ });
104
+ return id;
105
+ }
106
+ if (d.notation === 'need') {
107
+ canon.needs.push({ id, name: str(d.name) ?? id });
108
+ return id;
109
+ }
110
+ if (d.notation === 'validation') {
111
+ const validates = str(d.validates);
112
+ const method = str(d.method) as ValidationMethod | undefined;
113
+ const outcome = str(d.outcome) as ValidationOutcome | undefined;
114
+ if (!validates || !method || !outcome) return null;
115
+ canon.validations.push({
116
+ id, validates, method, outcome,
117
+ performed_at: str(d.performed_at),
118
+ evidenceCount: Array.isArray(d.evidence) ? d.evidence.length : 0,
119
+ admitted_at: str(d.admitted_at),
82
120
  });
83
121
  return id;
84
122
  }
@@ -98,6 +136,19 @@ export function ingestComplianceDoc(canon: ComplianceCanon, doc: unknown): strin
98
136
  });
99
137
  return id;
100
138
  }
139
+ if (d.notation === 'verification') {
140
+ const verifies = str(d.verifies);
141
+ const method = str(d.method) as VerificationMethod | undefined;
142
+ const outcome = str(d.outcome) as VerificationOutcome | undefined;
143
+ if (!verifies || !method || !outcome) return null;
144
+ canon.verifications.push({
145
+ id, verifies, method, outcome,
146
+ performed_at: str(d.performed_at),
147
+ evidenceCount: Array.isArray(d.evidence) ? d.evidence.length : 0,
148
+ admitted_at: str(d.admitted_at),
149
+ });
150
+ return id;
151
+ }
101
152
  if (d.zone === 'codex') {
102
153
  canon.codex.push({ id, name: str(d.name) ?? id, type: str(d.type), jurisdiction: str(d.jurisdiction) });
103
154
  return id;
@@ -1,4 +1,4 @@
1
- // Confidence bridge for the compliance views (vkgeorgia/strategy#162 — DQ-2).
1
+ // Confidence bridge for the compliance views (DQ-2).
2
2
  // Maps the compliance index projections (IndexRequirement / IndexAssertion) onto
3
3
  // the DQ-1 ScoringElement shape, scores the view, and renders the §11.6 header
4
4
  // line. Lives in @transitrix/diagrams so Studio and DSM share one bridge and
@@ -1,6 +1,7 @@
1
- // Gap dashboard report (vkgeorgia/strategy#84 Phase 4 / CV-5).
1
+ // Gap dashboard report (Phase 4 / CV-5; REQ-VERIF-COVERAGE-001/002 per
2
+ // 15-requirement.md §4).
2
3
  //
3
- // Four operational gap lists computed from the shared reverse-index (Phase 3):
4
+ // Operational gap lists computed from the shared reverse-index (Phase 3):
4
5
  // 1. Requirements with no Assertion targeting them (severity-sorted).
5
6
  // 2. Assertions without evidence — the ASSERT-007 case: status ∈
6
7
  // {compliant, partial} AND no evidence. `under_review` / `non_compliant` /
@@ -9,9 +10,18 @@
9
10
  // 3. Stale Assertions — the ASSERT-008 case: `next_review_at` is in the past.
10
11
  // 4. Past-deadline requirements (CV-5) — REQUIREMENT.deadline < today AND
11
12
  // the requirement has no fully-compliant assertion. Deadline-missed gaps.
13
+ // 5. Requirements with no Verification targeting them — REQ-VERIF-COVERAGE-001
14
+ // (27-verification.md §5 / 15-requirement.md §4). Independent of #1 — the
15
+ // ASSERTION and VERIFICATION catalogues never disagree with each other by
16
+ // construction; a requirement may have one, both, or neither.
17
+ // 6. Requirements whose verifications exist but never closed — every one is
18
+ // still `not_yet_run` / `inconclusive` — REQ-VERIF-COVERAGE-002. Mutually
19
+ // exclusive with #5 by construction.
12
20
 
13
- import type { ComplianceIndex, IndexAssertion, IndexRequirement } from './types.js';
21
+ import type { ComplianceIndex, IndexAssertion, IndexRequirement, IndexNeed } from './types.js';
14
22
  import { computeDeadlineStatus } from './impact.js';
23
+ import { CLOSED_VERIFICATION_OUTCOMES } from '../verification/types.js';
24
+ import { CLOSED_VALIDATION_OUTCOMES } from '../validation/types.js';
15
25
 
16
26
  export interface GapReport {
17
27
  /** Requirements with no assertion about them, severity-sorted then id. */
@@ -26,6 +36,36 @@ export interface GapReport {
26
36
  * (oldest first). Empty when `today` is not supplied.
27
37
  */
28
38
  pastDeadlineRequirements: IndexRequirement[];
39
+ /**
40
+ * REQ-VERIF-COVERAGE-001: requirements with no VERIFICATION targeting them,
41
+ * severity-sorted then id — the engineering V&V analogue of
42
+ * `requirementsWithoutAssertions`.
43
+ */
44
+ requirementsWithoutVerification: IndexRequirement[];
45
+ /**
46
+ * REQ-VERIF-COVERAGE-002: requirements with one or more VERIFICATIONs
47
+ * targeting them, but none closed (`pass`/`fail`) — every one is still
48
+ * `not_yet_run`/`inconclusive`. Severity-sorted then id.
49
+ */
50
+ requirementsWithUnresolvedVerification: IndexRequirement[];
51
+ /**
52
+ * NEED-COVERAGE-001: needs with no REQUIREMENT whose `serves` names them —
53
+ * the NEED-side analogue of `requirementsWithoutAssertions`, id-sorted.
54
+ */
55
+ needsWithoutRequirements: IndexNeed[];
56
+ /**
57
+ * NEED-VALIDATION-COVERAGE-001: needs with no VALIDATION targeting them,
58
+ * id-sorted — the validation-side analogue of
59
+ * `requirementsWithoutVerification`.
60
+ */
61
+ needsWithoutValidation: IndexNeed[];
62
+ /**
63
+ * NEED-VALIDATION-COVERAGE-002: needs with one or more VALIDATIONs
64
+ * targeting them, but none closed (`pass`/`fail`) — every one is still
65
+ * `not_yet_run`/`inconclusive`. Mutually exclusive with
66
+ * `needsWithoutValidation` by construction. Id-sorted.
67
+ */
68
+ needsWithUnresolvedValidation: IndexNeed[];
29
69
  }
30
70
 
31
71
  export interface GapReportOptions {
@@ -90,5 +130,52 @@ export function buildGapReport(index: ComplianceIndex, options: GapReportOptions
90
130
  })
91
131
  : [];
92
132
 
93
- return { requirementsWithoutAssertions, assertionsWithoutEvidence, staleAssertions, pastDeadlineRequirements };
133
+ // REQ-VERIF-COVERAGE-001 — no VERIFICATION targets the requirement at all.
134
+ const requirementsWithoutVerification = [...index.requirementById.values()]
135
+ .filter(r => (index.verificationsByRequirement.get(r.id) ?? []).length === 0)
136
+ .sort((a, b) => {
137
+ const d = severityRank(a.severity) - severityRank(b.severity);
138
+ return d !== 0 ? d : a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
139
+ });
140
+
141
+ // REQ-VERIF-COVERAGE-002 — has verifications, but none closed (pass/fail).
142
+ // Mutually exclusive with -001 by construction (only requirements with at
143
+ // least one verification are considered here).
144
+ const requirementsWithUnresolvedVerification = [...index.requirementById.values()]
145
+ .filter(r => {
146
+ const verifications = index.verificationsByRequirement.get(r.id) ?? [];
147
+ return verifications.length > 0
148
+ && !verifications.some(v => (CLOSED_VERIFICATION_OUTCOMES as readonly string[]).includes(v.outcome));
149
+ })
150
+ .sort((a, b) => {
151
+ const d = severityRank(a.severity) - severityRank(b.severity);
152
+ return d !== 0 ? d : a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
153
+ });
154
+
155
+ // NEED-COVERAGE-001 — no REQUIREMENT.serves names this need.
156
+ const needsWithoutRequirements = [...index.needById.values()]
157
+ .filter(n => (index.requirementsByNeed.get(n.id) ?? []).length === 0)
158
+ .sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
159
+
160
+ // NEED-VALIDATION-COVERAGE-001 — no VALIDATION targets the need at all.
161
+ const needsWithoutValidation = [...index.needById.values()]
162
+ .filter(n => (index.validationsByNeed.get(n.id) ?? []).length === 0)
163
+ .sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
164
+
165
+ // NEED-VALIDATION-COVERAGE-002 — has validations, but none closed (pass/fail).
166
+ // Mutually exclusive with -001 by construction (only needs with at least
167
+ // one validation are considered here).
168
+ const needsWithUnresolvedValidation = [...index.needById.values()]
169
+ .filter(n => {
170
+ const validations = index.validationsByNeed.get(n.id) ?? [];
171
+ return validations.length > 0
172
+ && !validations.some(v => (CLOSED_VALIDATION_OUTCOMES as readonly string[]).includes(v.outcome));
173
+ })
174
+ .sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
175
+
176
+ return {
177
+ requirementsWithoutAssertions, assertionsWithoutEvidence, staleAssertions, pastDeadlineRequirements,
178
+ requirementsWithoutVerification, requirementsWithUnresolvedVerification,
179
+ needsWithoutRequirements, needsWithoutValidation, needsWithUnresolvedValidation,
180
+ };
94
181
  }
@@ -1,4 +1,4 @@
1
- // HTML renderers for the compliance views (vkgeorgia/strategy#84 Phase 5,
1
+ // HTML renderers for the compliance views (Phase 5,
2
2
  // PDF follow-on). Pure string builders — fed to WeasyPrint by the
3
3
  // `transitrix export-compliance --format pdf` path in src/export-compliance.ts.
4
4
  //
@@ -183,11 +183,13 @@ export function renderComplianceHtml(canon: ComplianceCanon, scope: ReportScope,
183
183
  }
184
184
 
185
185
  case 'gap': {
186
- const index = buildComplianceIndex({ requirements: canon.requirements, assertions: canon.assertions });
186
+ const index = buildComplianceIndex({
187
+ requirements: canon.requirements,
188
+ assertions: canon.assertions,
189
+ verifications: canon.verifications,
190
+ });
187
191
  const report = buildGapReport(index, { today: options.today });
188
192
  const title = options.title ?? 'Compliance Gap Dashboard';
189
- const total = report.requirementsWithoutAssertions.length + report.assertionsWithoutEvidence.length + report.staleAssertions.length;
190
- const summary = `<p class="cmp-summary">${total} gap(s)</p>`;
191
193
  const section = (heading: string, count: number, items: string[]): string => {
192
194
  const body = items.length === 0
193
195
  ? '<p class="cmp-ok">✓ none</p>'
@@ -206,13 +208,23 @@ export function renderComplianceHtml(canon: ComplianceCanon, scope: ReportScope,
206
208
  const pastDl = report.pastDeadlineRequirements.map(r => (
207
209
  `<li><code>${escHtml(r.id)}</code> ${escHtml(r.name)} <span class="cmp-meta">deadline ${r.deadline ? escHtml(r.deadline) : '—'}${r.severity ? `, severity ${escHtml(r.severity)}` : ''}</span></li>`
208
210
  ));
209
- const total4 = report.requirementsWithoutAssertions.length + report.assertionsWithoutEvidence.length + report.staleAssertions.length + report.pastDeadlineRequirements.length;
211
+ const noVerif = report.requirementsWithoutVerification.map(r => (
212
+ `<li><code>${escHtml(r.id)}</code> ${escHtml(r.name)}${r.severity ? ` <span class="cmp-meta">severity ${escHtml(r.severity)}</span>` : ''}</li>`
213
+ ));
214
+ const unresolvedVerif = report.requirementsWithUnresolvedVerification.map(r => (
215
+ `<li><code>${escHtml(r.id)}</code> ${escHtml(r.name)}${r.severity ? ` <span class="cmp-meta">severity ${escHtml(r.severity)}</span>` : ''}</li>`
216
+ ));
217
+ const total6 = report.requirementsWithoutAssertions.length + report.assertionsWithoutEvidence.length
218
+ + report.staleAssertions.length + report.pastDeadlineRequirements.length
219
+ + report.requirementsWithoutVerification.length + report.requirementsWithUnresolvedVerification.length;
210
220
  const body = [
211
- `<p class="cmp-summary">${total4} gap(s) across 4 checks</p>`,
221
+ `<p class="cmp-summary">${total6} gap(s) across 6 checks</p>`,
212
222
  section('Requirements without assertions', report.requirementsWithoutAssertions.length, reqs),
213
223
  section('Assertions without evidence — ASSERT-007', report.assertionsWithoutEvidence.length, noEvidence),
214
224
  section('Stale assertions — ASSERT-008', report.staleAssertions.length, stale),
215
225
  section('Past-deadline requirements (CV-5)', report.pastDeadlineRequirements.length, pastDl),
226
+ section('Requirements without verification — REQ-VERIF-COVERAGE-001', report.requirementsWithoutVerification.length, noVerif),
227
+ section('Requirements with unresolved verification — REQ-VERIF-COVERAGE-002', report.requirementsWithUnresolvedVerification.length, unresolvedVerif),
216
228
  ].join('');
217
229
  return htmlDoc(title, options.today, body);
218
230
  }
@@ -230,7 +242,7 @@ export interface ImpactMatrixHtmlOptions {
230
242
  * Renders an `ImpactMatrix` (from `buildImpactMatrix`) as a self-contained
231
243
  * A4 HTML document suitable for WeasyPrint PDF output.
232
244
  *
233
- * Used by `cervin export-compliance --report <id> --format pdf`.
245
+ * Used by `transitrix export-compliance --report <id> --format pdf`.
234
246
  */
235
247
  export function renderImpactMatrixHtml(matrix: ImpactMatrix, options: ImpactMatrixHtmlOptions = {}): string {
236
248
  const STATUS_GLYPH: Partial<Record<string, string>> = {
@@ -1,5 +1,5 @@
1
1
  // Compliance-impact matrix derivation + markdown renderer
2
- // (vkgeorgia/strategy#166 — interim renderer; CV-1 view-config wiring
2
+ // (interim renderer; CV-1 view-config wiring
3
3
  // per strategy#84 decomposition refresh).
4
4
  //
5
5
  // Implements the render contract from
@@ -5,6 +5,8 @@ export { buildGapReport } from './gap-report.js';
5
5
  export type { GapReport, GapReportOptions } from './gap-report.js';
6
6
  export { emptyCanon, ingestComplianceDoc } from './classify.js';
7
7
  export type { ComplianceCanon, ComplianceProduct, ComplianceCodexDoc } from './classify.js';
8
+ export { CLOSED_VERIFICATION_OUTCOMES } from '../verification/types.js';
9
+ export { CLOSED_VALIDATION_OUTCOMES } from '../validation/types.js';
8
10
  export {
9
11
  admitDocumentToCatalog,
10
12
  buildComplianceScan,
@@ -58,6 +60,9 @@ export type {
58
60
  DeadlineStatus,
59
61
  IndexRequirement,
60
62
  IndexAssertion,
63
+ IndexVerification,
64
+ IndexNeed,
65
+ IndexValidation,
61
66
  LawTree,
62
67
  LawTreeRequirement,
63
68
  ProductView,
@@ -66,6 +71,7 @@ export type {
66
71
  ObjectDetailInput,
67
72
  RequirementTrace,
68
73
  TraceAssertionRow,
74
+ TraceVerificationRow,
69
75
  TraceElementCatalog,
70
76
  TraceElementRef,
71
77
  TraceSourceRef,
@@ -1,4 +1,4 @@
1
- // Markdown renderers for the compliance views (vkgeorgia/strategy#84 Phase 5).
1
+ // Markdown renderers for the compliance views (Phase 5).
2
2
  // Pure string builders consumed by the `transitrix export-compliance` CLI.
3
3
  // PDF rendering lives in `html.ts` (HTML doc fed to WeasyPrint by the CLI).
4
4
 
@@ -111,10 +111,16 @@ function productMarkdown(canon: ComplianceCanon, productId: string): string {
111
111
  }
112
112
 
113
113
  function gapMarkdown(canon: ComplianceCanon, today?: string): string {
114
- const index = buildComplianceIndex({ requirements: canon.requirements, assertions: canon.assertions });
114
+ const index = buildComplianceIndex({
115
+ requirements: canon.requirements,
116
+ assertions: canon.assertions,
117
+ verifications: canon.verifications,
118
+ });
115
119
  const report = buildGapReport(index, { today });
116
- const total = report.requirementsWithoutAssertions.length + report.assertionsWithoutEvidence.length + report.staleAssertions.length + report.pastDeadlineRequirements.length;
117
- const out: string[] = ['# Compliance Gap Dashboard', '', `_${total} gap(s) across 4 checks_`, ''];
120
+ const total = report.requirementsWithoutAssertions.length + report.assertionsWithoutEvidence.length
121
+ + report.staleAssertions.length + report.pastDeadlineRequirements.length
122
+ + report.requirementsWithoutVerification.length + report.requirementsWithUnresolvedVerification.length;
123
+ const out: string[] = ['# Compliance Gap Dashboard', '', `_${total} gap(s) across 6 checks_`, ''];
118
124
 
119
125
  out.push(`## Requirements without assertions (${report.requirementsWithoutAssertions.length})`, '');
120
126
  if (report.requirementsWithoutAssertions.length === 0) out.push('_✓ none_', '');
@@ -132,5 +138,13 @@ function gapMarkdown(canon: ComplianceCanon, today?: string): string {
132
138
  if (report.pastDeadlineRequirements.length === 0) out.push('_✓ none_', '');
133
139
  else { for (const r of report.pastDeadlineRequirements) out.push(`- [ ] \`${r.id}\` ${r.name} — deadline: ${r.deadline ?? '—'}${r.severity ? `, severity: ${r.severity}` : ''}`); out.push(''); }
134
140
 
141
+ out.push(`## Requirements without verification — REQ-VERIF-COVERAGE-001 (${report.requirementsWithoutVerification.length})`, '');
142
+ if (report.requirementsWithoutVerification.length === 0) out.push('_✓ none_', '');
143
+ else { for (const r of report.requirementsWithoutVerification) out.push(`- [ ] \`${r.id}\` ${r.name}${r.severity ? ` — severity: ${r.severity}` : ''}`); out.push(''); }
144
+
145
+ out.push(`## Requirements with unresolved verification — REQ-VERIF-COVERAGE-002 (${report.requirementsWithUnresolvedVerification.length})`, '');
146
+ if (report.requirementsWithUnresolvedVerification.length === 0) out.push('_✓ none_', '');
147
+ else { for (const r of report.requirementsWithUnresolvedVerification) out.push(`- [ ] \`${r.id}\` ${r.name}${r.severity ? ` — severity: ${r.severity}` : ''}`); out.push(''); }
148
+
135
149
  return out.join('\n');
136
150
  }
@@ -1,6 +1,14 @@
1
- // Reverse-index builder (vkgeorgia/strategy#84 Phase 3).
1
+ // Reverse-index builder (Phase 3).
2
2
 
3
- import type { ComplianceIndex, ComplianceIndexInput, IndexAssertion, IndexRequirement } from './types.js';
3
+ import type {
4
+ ComplianceIndex,
5
+ ComplianceIndexInput,
6
+ IndexAssertion,
7
+ IndexRequirement,
8
+ IndexVerification,
9
+ IndexNeed,
10
+ IndexValidation,
11
+ } from './types.js';
4
12
 
5
13
  function push<K, V>(map: Map<K, V[]>, key: K, value: V): void {
6
14
  const list = map.get(key);
@@ -12,7 +20,12 @@ function push<K, V>(map: Map<K, V[]>, key: K, value: V): void {
12
20
  * Builds the reverse indexes used by every compliance view. Pure; the input
13
21
  * arrays are read, not mutated. Requirements with no `derived_from` simply do
14
22
  * not appear under any law; assertions are indexed by both their requirement
15
- * (`about`) and their subject.
23
+ * (`about`) and their subject; verifications are indexed by their requirement
24
+ * (`verifies`) — an independent catalogue from assertions (27-verification.md §4).
25
+ * Needs are indexed by id; requirements are additionally indexed by the NEED
26
+ * they `serves` (15-requirement.md §2.7), and validations by the NEED they
27
+ * `validate` (28-validation.md §2) — both feed the NEED-side coverage rules
28
+ * (ELEMENT_PRIMITIVES.md §9).
16
29
  */
17
30
  export function buildComplianceIndex(input: ComplianceIndexInput): ComplianceIndex {
18
31
  const requirementById = new Map<string, IndexRequirement>();
@@ -20,16 +33,34 @@ export function buildComplianceIndex(input: ComplianceIndexInput): ComplianceInd
20
33
  const assertionsByRequirement = new Map<string, IndexAssertion[]>();
21
34
  const assertionsBySubject = new Map<string, IndexAssertion[]>();
22
35
  const requirementsByParent = new Map<string, IndexRequirement[]>();
36
+ const verificationsByRequirement = new Map<string, IndexVerification[]>();
37
+ const needById = new Map<string, IndexNeed>();
38
+ const requirementsByNeed = new Map<string, IndexRequirement[]>();
39
+ const validationsByNeed = new Map<string, IndexValidation[]>();
23
40
 
24
41
  for (const r of input.requirements) {
25
42
  requirementById.set(r.id, r);
26
43
  for (const law of r.derived_from ?? []) push(requirementsByLaw, law, r);
27
44
  if (r.parent) push(requirementsByParent, r.parent, r);
45
+ if (r.serves) push(requirementsByNeed, r.serves, r);
28
46
  }
29
47
  for (const a of input.assertions) {
30
48
  push(assertionsByRequirement, a.about, a);
31
49
  push(assertionsBySubject, a.subject, a);
32
50
  }
51
+ for (const v of input.verifications ?? []) {
52
+ push(verificationsByRequirement, v.verifies, v);
53
+ }
54
+ for (const n of input.needs ?? []) {
55
+ needById.set(n.id, n);
56
+ }
57
+ for (const v of input.validations ?? []) {
58
+ push(validationsByNeed, v.validates, v);
59
+ }
33
60
 
34
- return { requirementById, requirementsByLaw, assertionsByRequirement, assertionsBySubject, requirementsByParent };
61
+ return {
62
+ requirementById, requirementsByLaw, assertionsByRequirement, assertionsBySubject,
63
+ requirementsByParent, verificationsByRequirement,
64
+ needById, requirementsByNeed, validationsByNeed,
65
+ };
35
66
  }
@@ -22,6 +22,7 @@ import type {
22
22
  IndexRequirement,
23
23
  RequirementTrace,
24
24
  TraceAssertionRow,
25
+ TraceVerificationRow,
25
26
  TraceElementCatalog,
26
27
  TraceElementRef,
27
28
  TraceSourceRef,
@@ -95,10 +96,15 @@ export function buildRequirementTrace(
95
96
  realisedVia: (assertion.realised_via ?? []).map(rid => resolveElement(rid, catalog)),
96
97
  }));
97
98
 
99
+ const rawVerifications = index.verificationsByRequirement.get(requirementId) ?? [];
100
+ const verifications: TraceVerificationRow[] = [...rawVerifications]
101
+ .sort(byId)
102
+ .map(verification => ({ verification }));
103
+
98
104
  const ancestors = collectAncestors(requirement, index);
99
105
  const children = [...(index.requirementsByParent.get(requirementId) ?? [])].sort(byId);
100
106
 
101
- return { requirement, sources, assertions, ancestors, children };
107
+ return { requirement, sources, assertions, verifications, ancestors, children };
102
108
  }
103
109
 
104
110
  /**
@@ -1,9 +1,10 @@
1
- // Shared compliance reverse-index + derived views (vkgeorgia/strategy#84
2
- // Phase 3). The reverse-index pass (Law → Requirements, Requirement →
1
+ // Shared compliance reverse-index + derived views (Phase 3). The reverse-index pass (Law → Requirements, Requirement →
3
2
  // Assertions, Subject → Assertions) backs the single-law tree and the
4
3
  // single-product view here, and the gap dashboard in Phase 4.
5
4
 
6
5
  import type { AssertionStatus } from '../assertion/types.js';
6
+ import type { VerificationMethod, VerificationOutcome } from '../verification/types.js';
7
+ import type { ValidationMethod, ValidationOutcome } from '../validation/types.js';
7
8
  import type { ComplianceCodexDoc } from './classify.js';
8
9
 
9
10
  /** Projection of a REQUIREMENT (or CONSTRAINT) the compliance views need. */
@@ -53,6 +54,35 @@ export interface IndexRequirement {
53
54
  * Present when the author has scheduled a review; drives `REQ-STALE-001`.
54
55
  */
55
56
  next_review_at?: string;
57
+ /**
58
+ * Typed ID of the upstream `NEED` this requirement traces to
59
+ * (15-requirement.md §2.7; `REQ-SERVES-001`). Optional, singular. Feeds
60
+ * `NEED-COVERAGE-001` (ELEMENT_PRIMITIVES.md §9) on the NEED side.
61
+ */
62
+ serves?: string;
63
+ }
64
+
65
+ /** Projection of a `NEED` element (ELEMENT_PRIMITIVES.md §7.28) the compliance
66
+ * views need — just enough to list an unaddressed / unvalidated need. */
67
+ export interface IndexNeed {
68
+ id: string;
69
+ name: string;
70
+ }
71
+
72
+ /** Projection of a `VALIDATION` claim (28-validation.md §2) the compliance
73
+ * views need — the validation-domain peer of `IndexVerification`, anchored
74
+ * on NEED instead of REQUIREMENT. */
75
+ export interface IndexValidation {
76
+ id: string;
77
+ /** Typed ID of the NEED this validation targets. */
78
+ validates: string;
79
+ method: ValidationMethod;
80
+ outcome: ValidationOutcome;
81
+ performed_at?: string;
82
+ /** Number of evidence entries — feeds the VALID-006 gap. */
83
+ evidenceCount?: number;
84
+ /** Admission date (CONTRACT §6) — feeds DQ-1 freshness decay. */
85
+ admitted_at?: string;
56
86
  }
57
87
 
58
88
  // ── Temporal status (CV-3) ──────────────────────────────────────────────────
@@ -91,6 +121,20 @@ export interface IndexAssertion {
91
121
  owner_to_confirm?: string;
92
122
  }
93
123
 
124
+ /** Projection of a VERIFICATION the compliance views need (27-verification.md §2). */
125
+ export interface IndexVerification {
126
+ id: string;
127
+ /** Typed ID of the REQUIREMENT this verification targets. */
128
+ verifies: string;
129
+ method: VerificationMethod;
130
+ outcome: VerificationOutcome;
131
+ performed_at?: string;
132
+ /** Number of evidence entries — feeds the VERIF-006 gap. */
133
+ evidenceCount?: number;
134
+ /** Admission date (CONTRACT §6) — feeds DQ-1 freshness decay. */
135
+ admitted_at?: string;
136
+ }
137
+
94
138
  // ── Stage grouping (CV-3a) ──────────────────────────────────────────────────
95
139
 
96
140
  /** One stage (or task) element of a business object, used as a matrix sub-column. */
@@ -128,6 +172,12 @@ export interface ObjectDetailInput {
128
172
  export interface ComplianceIndexInput {
129
173
  requirements: IndexRequirement[];
130
174
  assertions: IndexAssertion[];
175
+ /** Optional — absent inputs (existing callers) simply index no verifications. */
176
+ verifications?: IndexVerification[];
177
+ /** Optional — absent inputs (existing callers) simply index no needs. */
178
+ needs?: IndexNeed[];
179
+ /** Optional — absent inputs (existing callers) simply index no validations. */
180
+ validations?: IndexValidation[];
131
181
  }
132
182
 
133
183
  /** The reverse-index — all maps are keyed by canonical id. */
@@ -144,6 +194,18 @@ export interface ComplianceIndex {
144
194
  * (15-requirement.md §2.4). Enables the requirement-hierarchy view.
145
195
  */
146
196
  requirementsByParent: Map<string, IndexRequirement[]>;
197
+ /** Requirement id → verifications whose `verifies` names it
198
+ * (27-verification.md §2). The engineering V&V analogue of
199
+ * `assertionsByRequirement` — the two catalogues are independent. */
200
+ verificationsByRequirement: Map<string, IndexVerification[]>;
201
+ /** NEED id → needs, keyed by their own id (single-entry lookup convenience). */
202
+ needById: Map<string, IndexNeed>;
203
+ /** NEED id → requirements whose `serves` names it (15-requirement.md §2.7).
204
+ * Feeds `NEED-COVERAGE-001`. */
205
+ requirementsByNeed: Map<string, IndexRequirement[]>;
206
+ /** NEED id → validations whose `validates` names it (28-validation.md §2).
207
+ * Feeds `NEED-VALIDATION-COVERAGE-001..002`. */
208
+ validationsByNeed: Map<string, IndexValidation[]>;
147
209
  }
148
210
 
149
211
  // ── Single-law tree ─────────────────────────────────────────────────────────
@@ -198,6 +260,13 @@ export interface TraceAssertionRow {
198
260
  realisedVia: TraceElementRef[];
199
261
  }
200
262
 
263
+ /** One VERIFICATION targeting the traced requirement (27-verification.md §2).
264
+ * No subject/realised_via — a verification claims a protocol was run against
265
+ * the requirement's acceptance criteria directly (16-assertion.md §4). */
266
+ export interface TraceVerificationRow {
267
+ verification: IndexVerification;
268
+ }
269
+
201
270
  /**
202
271
  * Requirement traceability + hierarchy view.
203
272
  *
@@ -219,6 +288,10 @@ export interface RequirementTrace {
219
288
  sources: TraceSourceRef[];
220
289
  /** Forward-trace via ASSERTION. Empty for CONSTRAINT elements (16-assertion.md §1) or for a REQUIREMENT with no filed assertion. */
221
290
  assertions: TraceAssertionRow[];
291
+ /** Forward-trace via VERIFICATION (27-verification.md §2). Empty for CONSTRAINT
292
+ * elements (the engineering V&V leg applies to REQUIREMENT only, same posture
293
+ * as ASSERTION) or for a REQUIREMENT with no filed verification. */
294
+ verifications: TraceVerificationRow[];
222
295
  /** Parent chain: immediate parent first, root last. Empty when the element has no `parent`. */
223
296
  ancestors: IndexRequirement[];
224
297
  /** Direct children: elements naming this one as their `parent`, id-sorted. */
@@ -1,4 +1,4 @@
1
- // Single-law tree + single-product view builders (vkgeorgia/strategy#84 Phase 3).
1
+ // Single-law tree + single-product view builders (Phase 3).
2
2
 
3
3
  import type {
4
4
  ComplianceIndex,
@@ -1,4 +1,4 @@
1
- // Compliance matrix model (vkgeorgia/strategy#84 Phase 2).
1
+ // Compliance matrix model (Phase 2).
2
2
  //
3
3
  // The matrix is Products × Requirements. Each cell is the status of the
4
4
  // (Product, Requirement) ASSERTION when one exists, or a gap when none does.
@@ -97,6 +97,6 @@ export interface MatrixFilter {
97
97
  statuses?: AssertionStatus[];
98
98
  /** Keep only requirement columns whose resolved jurisdictions intersect this
99
99
  * set. Requirements with no resolved jurisdiction are filtered out when this
100
- * is non-empty (F16, epic #84). */
100
+ * is non-empty (F16). */
101
101
  jurisdictions?: string[];
102
102
  }
@@ -2,7 +2,7 @@
2
2
  // callers (Studio previews, DSM React) pass already-resolved element inputs
3
3
  // and a `today` reference; this module returns the per-element scores and
4
4
  // the view composite. Lives in @transitrix/diagrams so Studio and DSM share
5
- // one implementation (see vkgeorgia/strategy#159 — DQ-1).
5
+ // one implementation (DQ-1).
6
6
 
7
7
  import type {
8
8
  ConfidenceBand,
@@ -3,6 +3,7 @@
3
3
 
4
4
  import type { ValidationError, ValidationWarning, ValidationResult } from '../validation-types.js';
5
5
  import { typeOfId, isCanonicalIdOfType, type CanonCatalog } from '../typed-id.js';
6
+ import { checkAgreement } from '../agreement.js';
6
7
  import { CONSTRAINT_STATUSES, CONSTRAINT_SEVERITIES } from './types.js';
7
8
 
8
9
  export interface ConstraintValidateOptions {
@@ -127,5 +128,8 @@ export function validateConstraint(
127
128
  }
128
129
  }
129
130
 
131
+ // AGREE-001..003 — agreement axis (CONTRACT.md §6.3.1), optional; absent ⇒ agreed.
132
+ errors.push(...checkAgreement(c));
133
+
130
134
  return { valid: errors.length === 0, errors, warnings };
131
135
  }
package/src/edge-path.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  // arrow reads as perpendicular to the node's vertical edge.
6
6
  //
7
7
  // Pulling the path math here (rather than duplicating it inline in each
8
- // preview) makes the configurable-curvature behaviour (vkgeorgia/strategy#76)
8
+ // preview) makes the configurable-curvature behaviour
9
9
  // unit-testable — the extension has no test harness.
10
10
 
11
11
  /**
@@ -36,8 +36,8 @@ export function validateFactor(input: unknown, options: FactorValidateOptions =
36
36
  const f = input as Record<string, unknown>;
37
37
 
38
38
  // FACTOR-001 — id grammar + notation tag + envelope.
39
- if (!isCanonicalIdOfType(f.id, 'FACTOR')) {
40
- errors.push({ code: 'FACTOR-001', message: `id "${String(f.id)}" must match FACTOR-[<middle>-]<INTEGER>.`, path: 'id' });
39
+ if (!isCanonicalIdOfType(f.id, 'DRIVER')) {
40
+ errors.push({ code: 'FACTOR-001', message: `id "${String(f.id)}" must match DRIVER-[<middle>-]<INTEGER>.`, path: 'id' });
41
41
  }
42
42
  if (f.notation !== 'driver') {
43
43
  errors.push({ code: 'FACTOR-001', message: 'notation must be the fixed value "driver".', path: 'notation' });
@@ -1,4 +1,4 @@
1
- // Chain-table model for the FGCA / FGA previews (vkgeorgia/strategy#137).
1
+ // Chain-table model for the FGCA / FGA previews.
2
2
  //
3
3
  // The tree/chain preview (`layoutFGCAPreview`) shows Driver → Goal → Change →
4
4
  // Activity as columns of nodes joined by edges. This module produces the