@ecoma-io/archkeep 0.20.1 → 0.21.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.
- package/package.json +1 -1
- package/src/commands/completeness.mjs +601 -0
- package/src/commands/evaluation-primitives.mjs +499 -0
- package/src/commands/impact-statement.mjs +31 -409
- package/src/commands/impact.mjs +5 -4
- package/src/commands/provenance-command.mjs +33 -2
- package/src/commands/scenario-evaluation.mjs +284 -196
- package/src/commands/scenario.mjs +4 -3
- package/src/governance/provenance-graph.mjs +479 -0
- package/src/report/provenance-text.mjs +30 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecoma-io/archkeep",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Architecture enforcement for polyglot repositories — dependency graphs and module boundaries for Go, Rust, Python, TypeScript, JavaScript, Vue, Java and Kotlin",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"architecture",
|
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared completeness model used by both impact-statement and scenario-evaluation.
|
|
3
|
+
*
|
|
4
|
+
* Both the impact statement (`./impact-statement.mjs`) and scenario evaluation
|
|
5
|
+
* (`./scenario-evaluation.mjs`) compose deterministic evaluations into a single
|
|
6
|
+
* statement about what a change touches. This module provides the shared
|
|
7
|
+
* completeness vocabulary so two callers cannot drift.
|
|
8
|
+
*
|
|
9
|
+
* ## Evidence-Complete contract
|
|
10
|
+
*
|
|
11
|
+
* An evaluation is Evidence-Complete only when ALL required gates pass:
|
|
12
|
+
*
|
|
13
|
+
* - domainCoverage === 1
|
|
14
|
+
* - claimEvidenceCoverage === 1
|
|
15
|
+
* - causalCoverage === 1
|
|
16
|
+
* - provenanceCoverage === 1
|
|
17
|
+
* - mutationCoverage === 1
|
|
18
|
+
* - surfaceParity === 1
|
|
19
|
+
* - hiddenGapCount === 0
|
|
20
|
+
* - falseCompleteCount === 0
|
|
21
|
+
* - baseIdentityValid === true
|
|
22
|
+
* - deterministic === true
|
|
23
|
+
*
|
|
24
|
+
* `overallComplete` implies ALL gates pass. Any failed gate MUST prevent
|
|
25
|
+
* `overallComplete = true`.
|
|
26
|
+
*
|
|
27
|
+
* @module
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Evaluation status constants
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
/** @type {Readonly<{EVALUATED: string, PARTIAL: string, NOT_EVALUATED: string, UNSUPPORTED: string, REFUSED: string}>} */
|
|
35
|
+
export const EVALUATION_STATUS = Object.freeze({
|
|
36
|
+
EVALUATED: "evaluated",
|
|
37
|
+
PARTIAL: "partial",
|
|
38
|
+
NOT_EVALUATED: "not_evaluated",
|
|
39
|
+
UNSUPPORTED: "unsupported",
|
|
40
|
+
REFUSED: "refused",
|
|
41
|
+
});
|
|
42
|
+
export const EVALUATED = EVALUATION_STATUS.EVALUATED;
|
|
43
|
+
export const PARTIAL = EVALUATION_STATUS.PARTIAL;
|
|
44
|
+
export const NOT_EVALUATED = EVALUATION_STATUS.NOT_EVALUATED;
|
|
45
|
+
export const UNSUPPORTED = EVALUATION_STATUS.UNSUPPORTED;
|
|
46
|
+
export const REFUSED = EVALUATION_STATUS.REFUSED;
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Evidence-Complete gate names — the canonical roster
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The canonical roster of required gates for Evidence-Complete.
|
|
54
|
+
* Each gate has a key, a human-readable label, and a type.
|
|
55
|
+
* @type {Readonly<{key: string, label: string, type: string}[]>}
|
|
56
|
+
*/
|
|
57
|
+
export const EVIDENCE_COMPLETE_GATES = Object.freeze([
|
|
58
|
+
{ key: "domainCoverage", label: "Domain coverage", type: "ratio" },
|
|
59
|
+
{ key: "claimEvidenceCoverage", label: "Claim evidence coverage", type: "ratio" },
|
|
60
|
+
{ key: "causalCoverage", label: "Causal coverage", type: "ratio" },
|
|
61
|
+
{ key: "provenanceCoverage", label: "Provenance coverage", type: "ratio" },
|
|
62
|
+
{ key: "mutationCoverage", label: "Mutation coverage", type: "ratio" },
|
|
63
|
+
{ key: "surfaceParity", label: "Surface parity", type: "ratio" },
|
|
64
|
+
{ key: "hiddenGapCount", label: "Hidden gap count", type: "count" },
|
|
65
|
+
{ key: "falseCompleteCount", label: "False-complete count", type: "count" },
|
|
66
|
+
{ key: "baseIdentityValid", label: "Base identity valid", type: "boolean" },
|
|
67
|
+
{ key: "deterministic", label: "Deterministic", type: "boolean" },
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @typedef {object} EvidenceCompleteContract
|
|
72
|
+
* @property {number} domainCoverage Ratio of evaluated required domains to required domains (0-1).
|
|
73
|
+
* @property {number} claimEvidenceCoverage Ratio of claims with valid evidence to material claims (0-1).
|
|
74
|
+
* @property {number} causalCoverage Ratio of consequences with complete causal chain to material consequences (0-1).
|
|
75
|
+
* @property {number} provenanceCoverage Ratio of authoritative inputs with verified provenance to total authoritative inputs (0-1).
|
|
76
|
+
* @property {number} mutationCoverage Ratio of mutations with explicit outcome and evidence to requested mutations (0-1).
|
|
77
|
+
* @property {number} surfaceParity Ratio of surfaces with equivalent semantic output to total surfaces (0-1).
|
|
78
|
+
* @property {number} hiddenGapCount Number of required domains/inputs/claims/consequences/mutations not actually evaluated or evidenced but not explicitly reported.
|
|
79
|
+
* @property {number} falseCompleteCount Number of times overallComplete was true while required gates failed.
|
|
80
|
+
* @property {boolean} baseIdentityValid Whether the base identity was verified (not merely attributed).
|
|
81
|
+
* @property {boolean} deterministic Whether repeated evaluations produce semantically equivalent results.
|
|
82
|
+
* @property {string} overallStatus Overall Evidence-Complete status: "complete" | "incomplete" | "not_evaluated".
|
|
83
|
+
* @property {boolean} overallComplete True only when ALL required gates pass.
|
|
84
|
+
* @property {object} gates Individual gate statuses, keyed by gate name.
|
|
85
|
+
* @property {object} gates.domainCoverage Gate status.
|
|
86
|
+
* @property {object} gates.claimEvidenceCoverage Gate status.
|
|
87
|
+
* @property {object} gates.causalCoverage Gate status.
|
|
88
|
+
* @property {object} gates.provenanceCoverage Gate status.
|
|
89
|
+
* @property {object} gates.mutationCoverage Gate status.
|
|
90
|
+
* @property {object} gates.surfaceParity Gate status.
|
|
91
|
+
* @property {object} gates.hiddenGapCount Gate status.
|
|
92
|
+
* @property {object} gates.falseCompleteCount Gate status.
|
|
93
|
+
* @property {object} gates.baseIdentityValid Gate status.
|
|
94
|
+
* @property {object} gates.deterministic Gate status.
|
|
95
|
+
*/
|
|
96
|
+
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// Helpers
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Priority order for overall status computation (worst first).
|
|
103
|
+
* @type {string[]}
|
|
104
|
+
*/
|
|
105
|
+
const STATUS_ORDER = [
|
|
106
|
+
EVALUATION_STATUS.REFUSED,
|
|
107
|
+
EVALUATION_STATUS.UNSUPPORTED,
|
|
108
|
+
EVALUATION_STATUS.NOT_EVALUATED,
|
|
109
|
+
EVALUATION_STATUS.PARTIAL,
|
|
110
|
+
EVALUATION_STATUS.EVALUATED,
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Returns the worst (highest-priority) status among the given statuses.
|
|
115
|
+
*
|
|
116
|
+
* @param {...string} statuses One or more EVALUATION_STATUS values.
|
|
117
|
+
* @returns {string} The worst status.
|
|
118
|
+
*/
|
|
119
|
+
function worstStatus(...statuses) {
|
|
120
|
+
for (const candidate of STATUS_ORDER) {
|
|
121
|
+
if (statuses.includes(candidate)) return candidate;
|
|
122
|
+
}
|
|
123
|
+
return EVALUATION_STATUS.NOT_EVALUATED;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Creates a domain object from a status and optional note.
|
|
128
|
+
*
|
|
129
|
+
* @param {string} status One of EVALUATION_STATUS values.
|
|
130
|
+
* @param {string} [note] Optional explanatory note.
|
|
131
|
+
* @returns {{status: string, evaluated: boolean, partial: boolean, notEvaluated: boolean, unsupported: boolean, refused: boolean, note: string}}
|
|
132
|
+
*/
|
|
133
|
+
export function createDomain(status, note = "") {
|
|
134
|
+
return {
|
|
135
|
+
status,
|
|
136
|
+
evaluated: status === EVALUATION_STATUS.EVALUATED || status === EVALUATION_STATUS.PARTIAL,
|
|
137
|
+
partial: status === EVALUATION_STATUS.PARTIAL,
|
|
138
|
+
notEvaluated: status === EVALUATION_STATUS.NOT_EVALUATED,
|
|
139
|
+
unsupported: status === EVALUATION_STATUS.UNSUPPORTED,
|
|
140
|
+
refused: status === EVALUATION_STATUS.REFUSED,
|
|
141
|
+
note,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// Domain-level evaluation status
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Determines the evaluation status from a set of boolean flags.
|
|
151
|
+
*
|
|
152
|
+
* Priority order: refused > unsupported > notEvaluated > evaluated+partial.
|
|
153
|
+
* When no flag is set, defaults to NOT_EVALUATED.
|
|
154
|
+
*
|
|
155
|
+
* @param {object} [flags]
|
|
156
|
+
* @param {boolean} [flags.evaluated] Whether evaluation was performed.
|
|
157
|
+
* @param {boolean} [flags.partial] Whether evaluation was partial.
|
|
158
|
+
* @param {boolean} [flags.notEvaluated] Whether evaluation was not performed.
|
|
159
|
+
* @param {boolean} [flags.unsupported] Whether the domain is unsupported.
|
|
160
|
+
* @param {boolean} [flags.refused] Whether evaluation was refused.
|
|
161
|
+
* @returns {string} One of EVALUATION_STATUS values.
|
|
162
|
+
*/
|
|
163
|
+
export function evaluationStatus({ evaluated, partial, notEvaluated, unsupported, refused } = {}) {
|
|
164
|
+
if (refused) return EVALUATION_STATUS.REFUSED;
|
|
165
|
+
if (unsupported) return EVALUATION_STATUS.UNSUPPORTED;
|
|
166
|
+
if (notEvaluated) return EVALUATION_STATUS.NOT_EVALUATED;
|
|
167
|
+
if (evaluated) {
|
|
168
|
+
return partial ? EVALUATION_STATUS.PARTIAL : EVALUATION_STATUS.EVALUATED;
|
|
169
|
+
}
|
|
170
|
+
return EVALUATION_STATUS.NOT_EVALUATED;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
// Required domains — the declared evaluation contract
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* The authoritative set of required evaluation domains.
|
|
179
|
+
* Derived from repository doctrine: structural, constraints, boundaries,
|
|
180
|
+
* decisions, findings, debt, governance, evidence.
|
|
181
|
+
* @type {Readonly<string[]>}
|
|
182
|
+
*/
|
|
183
|
+
export const REQUIRED_DOMAINS = Object.freeze([
|
|
184
|
+
"structural",
|
|
185
|
+
"constraint",
|
|
186
|
+
"boundary",
|
|
187
|
+
"decision",
|
|
188
|
+
"findings",
|
|
189
|
+
"debt",
|
|
190
|
+
"governance",
|
|
191
|
+
"evidence",
|
|
192
|
+
]);
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Returns true when the domain status counts as evaluated for coverage purposes.
|
|
196
|
+
* PARTIAL does NOT count as evaluated — only EVALUATED does.
|
|
197
|
+
*
|
|
198
|
+
* @param {string} status EVALUATION_STATUS value
|
|
199
|
+
* @returns {boolean}
|
|
200
|
+
*/
|
|
201
|
+
export function isDomainEvaluated(status) {
|
|
202
|
+
return status === EVALUATION_STATUS.EVALUATED;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Computes domain coverage from the required domains and their statuses.
|
|
207
|
+
*
|
|
208
|
+
* domainCoverage = evaluatedRequiredDomains / requiredDomains
|
|
209
|
+
*
|
|
210
|
+
* @param {Object<string, string>} domainStatuses Map of domain name to EVALUATION_STATUS value.
|
|
211
|
+
* @param {readonly string[]} [requiredDomains] The required domain names (defaults to REQUIRED_DOMAINS).
|
|
212
|
+
* @returns {{coverage: number, evaluatedCount: number, requiredCount: number, failedDomains: string[]}}
|
|
213
|
+
*/
|
|
214
|
+
export function computeDomainCoverage(domainStatuses, requiredDomains = REQUIRED_DOMAINS) {
|
|
215
|
+
const failedDomains = [];
|
|
216
|
+
let evaluatedCount = 0;
|
|
217
|
+
|
|
218
|
+
for (const domain of requiredDomains) {
|
|
219
|
+
const status = domainStatuses[domain];
|
|
220
|
+
if (status && isDomainEvaluated(status)) {
|
|
221
|
+
evaluatedCount++;
|
|
222
|
+
} else {
|
|
223
|
+
failedDomains.push(domain);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const requiredCount = requiredDomains.length;
|
|
228
|
+
const coverage = requiredCount > 0 ? evaluatedCount / requiredCount : 1;
|
|
229
|
+
|
|
230
|
+
return { coverage, evaluatedCount, requiredCount, failedDomains };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
// Canonical completeness
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* @typedef {object} DomainStatus
|
|
239
|
+
* @property {string} status One of EVALUATION_STATUS values.
|
|
240
|
+
* @property {boolean} evaluated Whether evaluation was performed.
|
|
241
|
+
* @property {boolean} partial Whether evaluation was partial.
|
|
242
|
+
* @property {boolean} notEvaluated Whether evaluation was not performed.
|
|
243
|
+
* @property {boolean} unsupported Whether the domain is unsupported.
|
|
244
|
+
* @property {boolean} refused Whether evaluation was refused.
|
|
245
|
+
* @property {string} note Explanatory note about the status.
|
|
246
|
+
*/
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* @typedef {object} CompletenessResult
|
|
250
|
+
* @property {object} domains Domain-level statuses, keyed by domain name.
|
|
251
|
+
* @property {DomainStatus} domains.structural
|
|
252
|
+
* @property {DomainStatus} domains.constraint
|
|
253
|
+
* @property {DomainStatus} domains.boundary
|
|
254
|
+
* @property {DomainStatus} domains.decision
|
|
255
|
+
* @property {DomainStatus} domains.findings
|
|
256
|
+
* @property {DomainStatus} domains.debt
|
|
257
|
+
* @property {DomainStatus} domains.governance
|
|
258
|
+
* @property {DomainStatus} domains.evidence
|
|
259
|
+
* @property {boolean} overallComplete True only when ALL applicable domains are EVALUATED
|
|
260
|
+
* AND the Evidence-Complete contract is satisfied.
|
|
261
|
+
* @property {string} overallStatus One of EVALUATION_STATUS values.
|
|
262
|
+
* @property {EvidenceCompleteContract} [evidenceComplete] The Evidence-Complete contract,
|
|
263
|
+
* present when computed.
|
|
264
|
+
* @property {number} [hiddenGapCount] Number of domains NOT_EVALUATED without a note.
|
|
265
|
+
* @property {number} [falseCompleteCount] Number of false-complete detections.
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Builds canonical completeness from domain statuses.
|
|
269
|
+
* Now includes 8 domains (structural, constraint, boundary, decision,
|
|
270
|
+
* findings, debt, governance, evidence).
|
|
271
|
+
*
|
|
272
|
+
* @param {object} [input]
|
|
273
|
+
* @param {DomainStatus} [input.structural] The structural domain status.
|
|
274
|
+
* @param {DomainStatus} [input.constraint] The constraint domain status.
|
|
275
|
+
* @param {DomainStatus} [input.boundary] The boundary domain status.
|
|
276
|
+
* @param {DomainStatus} [input.decision] The decision domain status.
|
|
277
|
+
* @param {DomainStatus} [input.findings] The findings domain status.
|
|
278
|
+
* @param {DomainStatus} [input.debt] The debt domain status.
|
|
279
|
+
* @param {DomainStatus} [input.governance] The governance domain status.
|
|
280
|
+
* @param {DomainStatus} [input.evidence] The evidence domain status.
|
|
281
|
+
* @param {EvidenceCompleteContract} [input.evidenceComplete] Optional Evidence-Complete contract.
|
|
282
|
+
* @returns {CompletenessResult}
|
|
283
|
+
*/
|
|
284
|
+
export function buildCompleteness({
|
|
285
|
+
structural,
|
|
286
|
+
constraint,
|
|
287
|
+
boundary,
|
|
288
|
+
decision,
|
|
289
|
+
findings,
|
|
290
|
+
debt,
|
|
291
|
+
governance,
|
|
292
|
+
evidence,
|
|
293
|
+
evidenceComplete,
|
|
294
|
+
} = {}) {
|
|
295
|
+
const domains = {
|
|
296
|
+
structural: structural ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED),
|
|
297
|
+
constraint: constraint ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED),
|
|
298
|
+
boundary: boundary ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED),
|
|
299
|
+
decision: decision ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED),
|
|
300
|
+
findings: findings ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED),
|
|
301
|
+
debt: debt ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED),
|
|
302
|
+
governance: governance ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED),
|
|
303
|
+
evidence: evidence ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED),
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const statuses = Object.values(domains).map((d) => d.status);
|
|
307
|
+
const domainOverallComplete = statuses.every((s) => s === EVALUATION_STATUS.EVALUATED);
|
|
308
|
+
|
|
309
|
+
// If an Evidence-Complete contract is provided, enforce it as a gate
|
|
310
|
+
let ecComplete = true;
|
|
311
|
+
let falseCompleteCount = 0;
|
|
312
|
+
if (evidenceComplete) {
|
|
313
|
+
ecComplete = evidenceComplete.overallComplete;
|
|
314
|
+
falseCompleteCount = evidenceComplete.falseCompleteCount;
|
|
315
|
+
// Detect false complete: domain claims complete but Evidence-Complete fails
|
|
316
|
+
if (domainOverallComplete && !ecComplete) {
|
|
317
|
+
falseCompleteCount++;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Detect hidden gaps: a domain is NOT_EVALUATED but no note explains why
|
|
322
|
+
let hiddenGapCount = 0;
|
|
323
|
+
for (const [, domain] of Object.entries(domains)) {
|
|
324
|
+
if (domain.status === EVALUATION_STATUS.NOT_EVALUATED && !domain.note) {
|
|
325
|
+
hiddenGapCount++;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const overallComplete = domainOverallComplete && ecComplete;
|
|
330
|
+
const overallStatus = worstStatus(...statuses, ecComplete ? EVALUATED : NOT_EVALUATED);
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
domains,
|
|
334
|
+
overallComplete,
|
|
335
|
+
overallStatus,
|
|
336
|
+
...(evidenceComplete ? { evidenceComplete: { ...evidenceComplete, falseCompleteCount } } : {}),
|
|
337
|
+
...(hiddenGapCount > 0 ? { hiddenGapCount } : {}),
|
|
338
|
+
...(falseCompleteCount > 0 ? { falseCompleteCount } : {}),
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ---------------------------------------------------------------------------
|
|
343
|
+
// Evidence-Complete gate
|
|
344
|
+
// ---------------------------------------------------------------------------
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Builds an Evidence-Complete contract from the individual gate values.
|
|
348
|
+
*
|
|
349
|
+
* @param {object} gates
|
|
350
|
+
* @param {number} [gates.domainCoverage] Ratio (0-1).
|
|
351
|
+
* @param {number} [gates.claimEvidenceCoverage] Ratio (0-1).
|
|
352
|
+
* @param {number} [gates.causalCoverage] Ratio (0-1).
|
|
353
|
+
* @param {number} [gates.provenanceCoverage] Ratio (0-1).
|
|
354
|
+
* @param {number} [gates.mutationCoverage] Ratio (0-1).
|
|
355
|
+
* @param {number} [gates.surfaceParity] Ratio (0-1).
|
|
356
|
+
* @param {number} [gates.hiddenGapCount] Count (0 = pass).
|
|
357
|
+
* @param {number} [gates.falseCompleteCount] Count (0 = pass).
|
|
358
|
+
* @param {boolean} [gates.baseIdentityValid] Boolean (true = pass).
|
|
359
|
+
* @param {boolean} [gates.deterministic] Boolean (true = pass).
|
|
360
|
+
* @returns {EvidenceCompleteContract}
|
|
361
|
+
*/
|
|
362
|
+
export function buildEvidenceComplete({
|
|
363
|
+
domainCoverage = 0,
|
|
364
|
+
claimEvidenceCoverage = 0,
|
|
365
|
+
causalCoverage = 0,
|
|
366
|
+
provenanceCoverage = 0,
|
|
367
|
+
mutationCoverage = 0,
|
|
368
|
+
surfaceParity = 0,
|
|
369
|
+
hiddenGapCount = -1,
|
|
370
|
+
falseCompleteCount = -1,
|
|
371
|
+
baseIdentityValid = false,
|
|
372
|
+
deterministic = false,
|
|
373
|
+
} = {}) {
|
|
374
|
+
const gates = {
|
|
375
|
+
domainCoverage: { value: domainCoverage, pass: domainCoverage === 1 },
|
|
376
|
+
claimEvidenceCoverage: { value: claimEvidenceCoverage, pass: claimEvidenceCoverage === 1 },
|
|
377
|
+
causalCoverage: { value: causalCoverage, pass: causalCoverage === 1 },
|
|
378
|
+
provenanceCoverage: { value: provenanceCoverage, pass: provenanceCoverage === 1 },
|
|
379
|
+
mutationCoverage: { value: mutationCoverage, pass: mutationCoverage === 1 },
|
|
380
|
+
surfaceParity: { value: surfaceParity, pass: surfaceParity === 1 },
|
|
381
|
+
hiddenGapCount: { value: hiddenGapCount, pass: hiddenGapCount === 0 },
|
|
382
|
+
falseCompleteCount: { value: falseCompleteCount, pass: falseCompleteCount === 0 },
|
|
383
|
+
baseIdentityValid: { value: baseIdentityValid, pass: baseIdentityValid === true },
|
|
384
|
+
deterministic: { value: deterministic, pass: deterministic === true },
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
const allPass = Object.values(gates).every((g) => g.pass);
|
|
388
|
+
|
|
389
|
+
return {
|
|
390
|
+
domainCoverage,
|
|
391
|
+
claimEvidenceCoverage,
|
|
392
|
+
causalCoverage,
|
|
393
|
+
provenanceCoverage,
|
|
394
|
+
mutationCoverage,
|
|
395
|
+
surfaceParity,
|
|
396
|
+
hiddenGapCount,
|
|
397
|
+
falseCompleteCount,
|
|
398
|
+
baseIdentityValid,
|
|
399
|
+
deterministic,
|
|
400
|
+
overallStatus: allPass ? "complete" : "incomplete",
|
|
401
|
+
overallComplete: allPass,
|
|
402
|
+
gates,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Asserts that the Evidence-Complete contract is satisfied.
|
|
408
|
+
* Throws with a detailed message listing every failing gate.
|
|
409
|
+
*
|
|
410
|
+
* @param {EvidenceCompleteContract} ec The Evidence-Complete contract to verify.
|
|
411
|
+
* @returns {void}
|
|
412
|
+
* @throws {Error} When any gate fails.
|
|
413
|
+
*/
|
|
414
|
+
export function assertEvidenceComplete(ec) {
|
|
415
|
+
if (ec.overallComplete) return;
|
|
416
|
+
|
|
417
|
+
const failures = [];
|
|
418
|
+
for (const gate of EVIDENCE_COMPLETE_GATES) {
|
|
419
|
+
const g = ec.gates[gate.key];
|
|
420
|
+
if (!g.pass) {
|
|
421
|
+
failures.push(`${gate.label}: ${JSON.stringify(g.value)} (expected pass)`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
throw new Error(
|
|
426
|
+
`Evidence-Complete contract not satisfied.\n` +
|
|
427
|
+
` Overall: ${ec.overallStatus}\n` +
|
|
428
|
+
` Failed gates:\n ${failures.join("\n ")}`,
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// ---------------------------------------------------------------------------
|
|
433
|
+
// Governance completeness
|
|
434
|
+
// ---------------------------------------------------------------------------
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* @typedef {object} GovernanceCompletenessResult
|
|
438
|
+
* @property {DomainStatus} domain The governance domain status.
|
|
439
|
+
* @property {DomainStatus} findings The findings domain status.
|
|
440
|
+
* @property {DomainStatus} debt The debt domain status.
|
|
441
|
+
* @property {string} findingsStatus EVALUATION_STATUS for findings.
|
|
442
|
+
* @property {string} debtStatus EVALUATION_STATUS for debt.
|
|
443
|
+
* @property {number} findingsCount Number of findings.
|
|
444
|
+
* @property {number} debtCount Number of debt entries.
|
|
445
|
+
*/
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Builds governance completeness from findings and debt statuses.
|
|
449
|
+
*
|
|
450
|
+
* @param {object} [input]
|
|
451
|
+
* @param {string} [input.findingsStatus] EVALUATION_STATUS for findings.
|
|
452
|
+
* @param {string} [input.debtStatus] EVALUATION_STATUS for debt.
|
|
453
|
+
* @param {number} [input.findingsCount] Number of findings.
|
|
454
|
+
* @param {number} [input.debtCount] Number of debt entries.
|
|
455
|
+
* @returns {GovernanceCompletenessResult}
|
|
456
|
+
*/
|
|
457
|
+
export function buildGovernanceCompleteness({
|
|
458
|
+
findingsStatus = EVALUATION_STATUS.NOT_EVALUATED,
|
|
459
|
+
debtStatus = EVALUATION_STATUS.NOT_EVALUATED,
|
|
460
|
+
findingsCount = 0,
|
|
461
|
+
debtCount = 0,
|
|
462
|
+
} = {}) {
|
|
463
|
+
const findingsDomain = createDomain(findingsStatus);
|
|
464
|
+
const debtDomain = createDomain(debtStatus);
|
|
465
|
+
|
|
466
|
+
const status = worstStatus(findingsStatus, debtStatus);
|
|
467
|
+
|
|
468
|
+
let note = "";
|
|
469
|
+
if (status !== EVALUATION_STATUS.EVALUATED) {
|
|
470
|
+
const parts = [];
|
|
471
|
+
if (findingsStatus !== EVALUATION_STATUS.EVALUATED) {
|
|
472
|
+
parts.push(`findings: ${findingsStatus}`);
|
|
473
|
+
}
|
|
474
|
+
if (debtStatus !== EVALUATION_STATUS.EVALUATED) {
|
|
475
|
+
parts.push(`debt: ${debtStatus}`);
|
|
476
|
+
}
|
|
477
|
+
note = `Governance incomplete — ${parts.join(", ")}`;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return {
|
|
481
|
+
domain: createDomain(status, note),
|
|
482
|
+
findings: findingsDomain,
|
|
483
|
+
debt: debtDomain,
|
|
484
|
+
findingsStatus,
|
|
485
|
+
debtStatus,
|
|
486
|
+
findingsCount,
|
|
487
|
+
debtCount,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// ---------------------------------------------------------------------------
|
|
492
|
+
// Scenario completeness
|
|
493
|
+
// ---------------------------------------------------------------------------
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* @typedef {object} ScenarioCompletenessResult
|
|
497
|
+
* @property {object} domains Domain-level statuses (standard 5 domains).
|
|
498
|
+
* @property {boolean} overallComplete True only when ALL domains are EVALUATED,
|
|
499
|
+
* including scenario-specific domains.
|
|
500
|
+
* @property {string} overallStatus One of EVALUATION_STATUS values.
|
|
501
|
+
* @property {object} scenarioDomains Scenario-specific domain statuses.
|
|
502
|
+
* @property {DomainStatus} scenarioDomains.changes Whether all changes were applied.
|
|
503
|
+
* @property {DomainStatus} scenarioDomains.base Whether the base revision identity was verified.
|
|
504
|
+
* @property {DomainStatus} scenarioDomains.mutationCoverage Mutation coverage status.
|
|
505
|
+
* @property {EvidenceCompleteContract} [evidenceComplete] The Evidence-Complete contract.
|
|
506
|
+
*/
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Builds scenario completeness, extending canonical completeness with
|
|
510
|
+
* scenario-specific domains (changes, base identity, mutation coverage).
|
|
511
|
+
*
|
|
512
|
+
* In a scenario context, standard domains (structural, constraint, boundary,
|
|
513
|
+
* decision) pass through from the evaluation — they are NOT automatically
|
|
514
|
+
* EVALUATED. The scenario-specific domains capture whether changes were
|
|
515
|
+
* fully applied, whether the base revision identity was verified, and
|
|
516
|
+
* whether mutation coverage was complete.
|
|
517
|
+
*
|
|
518
|
+
* @param {object} [input]
|
|
519
|
+
* @param {boolean} [input.changesComplete] Whether all scenario changes were applied.
|
|
520
|
+
* @param {boolean} [input.baseIdentityVerified] Whether the base revision identity was verified.
|
|
521
|
+
* @param {boolean} [input.mutationCoverageComplete] Whether all mutations have explicit outcomes.
|
|
522
|
+
* @param {GovernanceCompletenessResult} [input.governance] Governance completeness
|
|
523
|
+
* from buildGovernanceCompleteness.
|
|
524
|
+
* @param {EvidenceCompleteContract} [input.evidenceComplete] Optional Evidence-Complete contract.
|
|
525
|
+
* @param {object} [input.domains] Existing domain statuses to pass through.
|
|
526
|
+
* @returns {ScenarioCompletenessResult}
|
|
527
|
+
*/
|
|
528
|
+
export function buildScenarioCompleteness({
|
|
529
|
+
changesComplete = true,
|
|
530
|
+
baseIdentityVerified = true,
|
|
531
|
+
mutationCoverageComplete = true,
|
|
532
|
+
governance,
|
|
533
|
+
evidenceComplete,
|
|
534
|
+
domains: existingDomains,
|
|
535
|
+
} = {}) {
|
|
536
|
+
const changesDomain = changesComplete
|
|
537
|
+
? createDomain(EVALUATION_STATUS.EVALUATED)
|
|
538
|
+
: createDomain(EVALUATION_STATUS.PARTIAL, "Some changes could not be applied");
|
|
539
|
+
|
|
540
|
+
const baseDomain = baseIdentityVerified
|
|
541
|
+
? createDomain(EVALUATION_STATUS.EVALUATED)
|
|
542
|
+
: createDomain(EVALUATION_STATUS.NOT_EVALUATED, "Base revision identity could not be verified");
|
|
543
|
+
|
|
544
|
+
const mutationDomain = mutationCoverageComplete
|
|
545
|
+
? createDomain(EVALUATION_STATUS.EVALUATED)
|
|
546
|
+
: createDomain(EVALUATION_STATUS.PARTIAL, "Not all mutations have explicit outcomes");
|
|
547
|
+
|
|
548
|
+
const governanceDomain =
|
|
549
|
+
governance?.domain ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED, "Governance not evaluated");
|
|
550
|
+
|
|
551
|
+
// Use existing domains if provided, otherwise create defaults
|
|
552
|
+
const structuralDomain =
|
|
553
|
+
existingDomains?.structural ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED);
|
|
554
|
+
const constraintDomain =
|
|
555
|
+
existingDomains?.constraint ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED);
|
|
556
|
+
const boundaryDomain = existingDomains?.boundary ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED);
|
|
557
|
+
const decisionDomain = existingDomains?.decision ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED);
|
|
558
|
+
const findingsDomain = existingDomains?.findings ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED);
|
|
559
|
+
const debtDomain = existingDomains?.debt ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED);
|
|
560
|
+
const evidenceDomain = existingDomains?.evidence ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED);
|
|
561
|
+
|
|
562
|
+
const baseResult = buildCompleteness({
|
|
563
|
+
structural: structuralDomain,
|
|
564
|
+
constraint: constraintDomain,
|
|
565
|
+
boundary: boundaryDomain,
|
|
566
|
+
decision: decisionDomain,
|
|
567
|
+
findings: findingsDomain,
|
|
568
|
+
debt: debtDomain,
|
|
569
|
+
governance: governanceDomain,
|
|
570
|
+
evidence: evidenceDomain,
|
|
571
|
+
evidenceComplete,
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
// Recompute overall with scenario domains included
|
|
575
|
+
const allStatuses = [
|
|
576
|
+
...Object.values(baseResult.domains).map((d) => d.status),
|
|
577
|
+
changesDomain.status,
|
|
578
|
+
baseDomain.status,
|
|
579
|
+
mutationDomain.status,
|
|
580
|
+
];
|
|
581
|
+
const overallComplete = allStatuses.every((s) => s === EVALUATION_STATUS.EVALUATED);
|
|
582
|
+
const overallStatus = worstStatus(...allStatuses);
|
|
583
|
+
|
|
584
|
+
return {
|
|
585
|
+
domains: baseResult.domains,
|
|
586
|
+
overallComplete,
|
|
587
|
+
overallStatus,
|
|
588
|
+
scenarioDomains: {
|
|
589
|
+
changes: changesDomain,
|
|
590
|
+
base: baseDomain,
|
|
591
|
+
mutationCoverage: mutationDomain,
|
|
592
|
+
},
|
|
593
|
+
...(baseResult.evidenceComplete ? { evidenceComplete: baseResult.evidenceComplete } : {}),
|
|
594
|
+
...(baseResult.hiddenGapCount !== undefined
|
|
595
|
+
? { hiddenGapCount: baseResult.hiddenGapCount }
|
|
596
|
+
: {}),
|
|
597
|
+
...(baseResult.falseCompleteCount !== undefined
|
|
598
|
+
? { falseCompleteCount: baseResult.falseCompleteCount }
|
|
599
|
+
: {}),
|
|
600
|
+
};
|
|
601
|
+
}
|