@ecoma-io/archkeep 0.20.1 → 0.22.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/cli.mjs +156 -66
- package/package.json +1 -1
- package/src/analysis/contract.md +32 -5
- package/src/analysis/source-util.mjs +107 -0
- package/src/analysis/typescript.mjs +86 -5
- package/src/commands/change.mjs +59 -28
- package/src/commands/check.mjs +65 -26
- package/src/commands/completeness.mjs +708 -0
- package/src/commands/context-command.mjs +13 -5
- package/src/commands/context.mjs +31 -4
- package/src/commands/coverage-verdict.mjs +184 -0
- package/src/commands/debt.mjs +18 -15
- package/src/commands/delta-classify.mjs +13 -18
- package/src/commands/delta.mjs +95 -33
- package/src/commands/diff.mjs +31 -24
- package/src/commands/discover.mjs +30 -10
- package/src/commands/drift.mjs +21 -21
- package/src/commands/edge-constraints.mjs +47 -1
- package/src/commands/evaluation-primitives.mjs +691 -0
- package/src/commands/evolution.mjs +27 -10
- package/src/commands/explain.mjs +14 -13
- package/src/commands/fitness.mjs +20 -19
- package/src/commands/graph.mjs +14 -5
- package/src/commands/health.mjs +12 -5
- package/src/commands/history.mjs +29 -15
- package/src/commands/impact-statement.mjs +31 -409
- package/src/commands/impact.mjs +18 -18
- package/src/commands/plan-context-command.mjs +10 -5
- package/src/commands/provenance-command.mjs +33 -2
- package/src/commands/reconcile.mjs +14 -17
- package/src/commands/scenario-evaluation.mjs +363 -198
- package/src/commands/scenario.mjs +32 -21
- package/src/commands/waivers.mjs +36 -28
- package/src/governance/evolution-event.mjs +62 -9
- package/src/governance/provenance-graph.mjs +479 -0
- package/src/intent/intent-manifest.json +83 -39
- package/src/report/json.mjs +32 -5
- package/src/report/provenance-text.mjs +30 -7
- package/src/report/text.mjs +82 -12
- package/src/verdict.mjs +78 -36
- package/src/workspace.mjs +126 -2
|
@@ -0,0 +1,708 @@
|
|
|
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
|
+
// Evaluation contract types — which gates are required per evaluation type
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The evaluation contract types that determine which Evidence-Complete gates
|
|
54
|
+
* are required for `overallComplete`.
|
|
55
|
+
*
|
|
56
|
+
* - `canonical`: Standard architecture evaluation (no mutations, no scenario).
|
|
57
|
+
* Gates NOT required: mutationCoverage, surfaceParity, baseIdentityValid.
|
|
58
|
+
* - `scenario`: Hypothetical scenario evaluation. ALL gates required.
|
|
59
|
+
*
|
|
60
|
+
* @type {Readonly<{CANONICAL: string, SCENARIO: string}>}
|
|
61
|
+
*/
|
|
62
|
+
export const EVALUATION_CONTRACT_TYPES = Object.freeze({
|
|
63
|
+
CANONICAL: "canonical",
|
|
64
|
+
SCENARIO: "scenario",
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Which Evidence-Complete gates are required for each contract type.
|
|
69
|
+
* A gate not listed here is still tracked and reported but does NOT block
|
|
70
|
+
* `overallComplete` — it is explicitly not applicable for that evaluation type.
|
|
71
|
+
*
|
|
72
|
+
* @type {Readonly<Object<string, ReadonlySet<string>>>}
|
|
73
|
+
*/
|
|
74
|
+
export const REQUIRED_GATES_FOR_CONTRACT = Object.freeze({
|
|
75
|
+
[EVALUATION_CONTRACT_TYPES.CANONICAL]: Object.freeze(
|
|
76
|
+
new Set([
|
|
77
|
+
"domainCoverage",
|
|
78
|
+
"claimEvidenceCoverage",
|
|
79
|
+
"causalCoverage",
|
|
80
|
+
"provenanceCoverage",
|
|
81
|
+
"hiddenGapCount",
|
|
82
|
+
"falseCompleteCount",
|
|
83
|
+
"deterministic",
|
|
84
|
+
]),
|
|
85
|
+
),
|
|
86
|
+
[EVALUATION_CONTRACT_TYPES.SCENARIO]: Object.freeze(
|
|
87
|
+
new Set([
|
|
88
|
+
"domainCoverage",
|
|
89
|
+
"claimEvidenceCoverage",
|
|
90
|
+
"causalCoverage",
|
|
91
|
+
"provenanceCoverage",
|
|
92
|
+
"mutationCoverage",
|
|
93
|
+
"surfaceParity",
|
|
94
|
+
"hiddenGapCount",
|
|
95
|
+
"falseCompleteCount",
|
|
96
|
+
"baseIdentityValid",
|
|
97
|
+
"deterministic",
|
|
98
|
+
]),
|
|
99
|
+
),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Returns true when the given gate key is required for the given contract type.
|
|
104
|
+
*
|
|
105
|
+
* @param {string} gateKey The gate key (e.g. "domainCoverage").
|
|
106
|
+
* @param {string} [contractType] The evaluation contract type.
|
|
107
|
+
* Defaults to SCENARIO (most restrictive).
|
|
108
|
+
* @returns {boolean}
|
|
109
|
+
*/
|
|
110
|
+
export function isGateRequired(gateKey, contractType = EVALUATION_CONTRACT_TYPES.SCENARIO) {
|
|
111
|
+
const required = REQUIRED_GATES_FOR_CONTRACT[contractType];
|
|
112
|
+
return required ? required.has(gateKey) : true;
|
|
113
|
+
}
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// Evidence-Complete gate names — the canonical roster
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The canonical roster of required gates for Evidence-Complete.
|
|
120
|
+
* Each gate has a key, a human-readable label, and a type.
|
|
121
|
+
* @type {Readonly<{key: string, label: string, type: string}[]>}
|
|
122
|
+
*/
|
|
123
|
+
export const EVIDENCE_COMPLETE_GATES = Object.freeze([
|
|
124
|
+
{ key: "domainCoverage", label: "Domain coverage", type: "ratio" },
|
|
125
|
+
{ key: "claimEvidenceCoverage", label: "Claim evidence coverage", type: "ratio" },
|
|
126
|
+
{ key: "causalCoverage", label: "Causal coverage", type: "ratio" },
|
|
127
|
+
{ key: "provenanceCoverage", label: "Provenance coverage", type: "ratio" },
|
|
128
|
+
{ key: "mutationCoverage", label: "Mutation coverage", type: "ratio" },
|
|
129
|
+
{ key: "surfaceParity", label: "Surface parity", type: "ratio" },
|
|
130
|
+
{ key: "hiddenGapCount", label: "Hidden gap count", type: "count" },
|
|
131
|
+
{ key: "falseCompleteCount", label: "False-complete count", type: "count" },
|
|
132
|
+
{ key: "baseIdentityValid", label: "Base identity valid", type: "boolean" },
|
|
133
|
+
{ key: "deterministic", label: "Deterministic", type: "boolean" },
|
|
134
|
+
]);
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @typedef {object} EvidenceCompleteContract
|
|
138
|
+
* @property {string} contractType The evaluation contract type (canonical | scenario).
|
|
139
|
+
* @property {number} domainCoverage Ratio of evaluated required domains to required domains (0-1).
|
|
140
|
+
* @property {number} claimEvidenceCoverage Ratio of claims with valid evidence to material claims (0-1).
|
|
141
|
+
* @property {number} causalCoverage Ratio of consequences with complete causal chain to material consequences (0-1).
|
|
142
|
+
* @property {number} provenanceCoverage Ratio of authoritative inputs with verified provenance to total authoritative inputs (0-1).
|
|
143
|
+
* @property {number} mutationCoverage Ratio of mutations with explicit outcome and evidence to requested mutations (0-1).
|
|
144
|
+
* @property {number} surfaceParity Ratio of surfaces with equivalent semantic output to total surfaces (0-1).
|
|
145
|
+
* @property {number} hiddenGapCount Number of required domains/inputs/claims/consequences/mutations not actually evaluated or evidenced but not explicitly reported.
|
|
146
|
+
* @property {number} falseCompleteCount Number of times overallComplete was true while required gates failed.
|
|
147
|
+
* @property {boolean} baseIdentityValid Whether the base identity was verified (not merely attributed).
|
|
148
|
+
* @property {boolean} deterministic Whether repeated evaluations produce semantically equivalent results.
|
|
149
|
+
* @property {string} overallStatus Overall Evidence-Complete status: "complete" | "incomplete" | "not_evaluated".
|
|
150
|
+
* @property {boolean} overallComplete True only when ALL required gates pass.
|
|
151
|
+
* @property {object} gates Individual gate statuses, keyed by gate name.
|
|
152
|
+
* @property {object} gates.domainCoverage Gate status with {value, pass, required}.
|
|
153
|
+
* @property {object} gates.claimEvidenceCoverage Gate status with {value, pass, required}.
|
|
154
|
+
* @property {object} gates.causalCoverage Gate status with {value, pass, required}.
|
|
155
|
+
* @property {object} gates.provenanceCoverage Gate status with {value, pass, required}.
|
|
156
|
+
* @property {object} gates.mutationCoverage Gate status with {value, pass, required}.
|
|
157
|
+
* @property {object} gates.surfaceParity Gate status with {value, pass, required}.
|
|
158
|
+
* @property {object} gates.hiddenGapCount Gate status with {value, pass, required}.
|
|
159
|
+
* @property {object} gates.falseCompleteCount Gate status with {value, pass, required}.
|
|
160
|
+
* @property {object} gates.baseIdentityValid Gate status with {value, pass, required}.
|
|
161
|
+
* @property {object} gates.deterministic Gate status with {value, pass, required}.
|
|
162
|
+
*/
|
|
163
|
+
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
// Helpers
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Priority order for overall status computation (worst first).
|
|
170
|
+
* @type {string[]}
|
|
171
|
+
*/
|
|
172
|
+
const STATUS_ORDER = [
|
|
173
|
+
EVALUATION_STATUS.REFUSED,
|
|
174
|
+
EVALUATION_STATUS.UNSUPPORTED,
|
|
175
|
+
EVALUATION_STATUS.NOT_EVALUATED,
|
|
176
|
+
EVALUATION_STATUS.PARTIAL,
|
|
177
|
+
EVALUATION_STATUS.EVALUATED,
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Returns the worst (highest-priority) status among the given statuses.
|
|
182
|
+
*
|
|
183
|
+
* @param {...string} statuses One or more EVALUATION_STATUS values.
|
|
184
|
+
* @returns {string} The worst status.
|
|
185
|
+
*/
|
|
186
|
+
function worstStatus(...statuses) {
|
|
187
|
+
for (const candidate of STATUS_ORDER) {
|
|
188
|
+
if (statuses.includes(candidate)) return candidate;
|
|
189
|
+
}
|
|
190
|
+
return EVALUATION_STATUS.NOT_EVALUATED;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Creates a domain object from a status and optional note.
|
|
195
|
+
*
|
|
196
|
+
* @param {string} status One of EVALUATION_STATUS values.
|
|
197
|
+
* @param {string} [note] Optional explanatory note.
|
|
198
|
+
* @returns {{status: string, evaluated: boolean, partial: boolean, notEvaluated: boolean, unsupported: boolean, refused: boolean, note: string}}
|
|
199
|
+
*/
|
|
200
|
+
export function createDomain(status, note = "") {
|
|
201
|
+
return {
|
|
202
|
+
status,
|
|
203
|
+
evaluated: status === EVALUATION_STATUS.EVALUATED || status === EVALUATION_STATUS.PARTIAL,
|
|
204
|
+
partial: status === EVALUATION_STATUS.PARTIAL,
|
|
205
|
+
notEvaluated: status === EVALUATION_STATUS.NOT_EVALUATED,
|
|
206
|
+
unsupported: status === EVALUATION_STATUS.UNSUPPORTED,
|
|
207
|
+
refused: status === EVALUATION_STATUS.REFUSED,
|
|
208
|
+
note,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
// Domain-level evaluation status
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Determines the evaluation status from a set of boolean flags.
|
|
218
|
+
*
|
|
219
|
+
* Priority order: refused > unsupported > notEvaluated > evaluated+partial.
|
|
220
|
+
* When no flag is set, defaults to NOT_EVALUATED.
|
|
221
|
+
*
|
|
222
|
+
* @param {object} [flags]
|
|
223
|
+
* @param {boolean} [flags.evaluated] Whether evaluation was performed.
|
|
224
|
+
* @param {boolean} [flags.partial] Whether evaluation was partial.
|
|
225
|
+
* @param {boolean} [flags.notEvaluated] Whether evaluation was not performed.
|
|
226
|
+
* @param {boolean} [flags.unsupported] Whether the domain is unsupported.
|
|
227
|
+
* @param {boolean} [flags.refused] Whether evaluation was refused.
|
|
228
|
+
* @returns {string} One of EVALUATION_STATUS values.
|
|
229
|
+
*/
|
|
230
|
+
export function evaluationStatus({ evaluated, partial, notEvaluated, unsupported, refused } = {}) {
|
|
231
|
+
if (refused) return EVALUATION_STATUS.REFUSED;
|
|
232
|
+
if (unsupported) return EVALUATION_STATUS.UNSUPPORTED;
|
|
233
|
+
if (notEvaluated) return EVALUATION_STATUS.NOT_EVALUATED;
|
|
234
|
+
if (evaluated) {
|
|
235
|
+
return partial ? EVALUATION_STATUS.PARTIAL : EVALUATION_STATUS.EVALUATED;
|
|
236
|
+
}
|
|
237
|
+
return EVALUATION_STATUS.NOT_EVALUATED;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
// Required domains — the declared evaluation contract
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* The authoritative set of required evaluation domains.
|
|
246
|
+
* Derived from repository doctrine: structural, constraints, boundaries,
|
|
247
|
+
* decisions, findings, debt, governance, evidence.
|
|
248
|
+
* @type {Readonly<string[]>}
|
|
249
|
+
*/
|
|
250
|
+
export const REQUIRED_DOMAINS = Object.freeze([
|
|
251
|
+
"structural",
|
|
252
|
+
"constraint",
|
|
253
|
+
"boundary",
|
|
254
|
+
"decision",
|
|
255
|
+
"findings",
|
|
256
|
+
"debt",
|
|
257
|
+
"governance",
|
|
258
|
+
"evidence",
|
|
259
|
+
]);
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Returns true when the domain status counts as evaluated for coverage purposes.
|
|
263
|
+
* PARTIAL does NOT count as evaluated — only EVALUATED does.
|
|
264
|
+
*
|
|
265
|
+
* @param {string} status EVALUATION_STATUS value
|
|
266
|
+
* @returns {boolean}
|
|
267
|
+
*/
|
|
268
|
+
export function isDomainEvaluated(status) {
|
|
269
|
+
return status === EVALUATION_STATUS.EVALUATED;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Computes domain coverage from the required domains and their statuses.
|
|
274
|
+
*
|
|
275
|
+
* domainCoverage = evaluatedRequiredDomains / requiredDomains
|
|
276
|
+
*
|
|
277
|
+
* @param {Object<string, string>} domainStatuses Map of domain name to EVALUATION_STATUS value.
|
|
278
|
+
* @param {readonly string[]} [requiredDomains] The required domain names (defaults to REQUIRED_DOMAINS).
|
|
279
|
+
* @returns {{coverage: number, evaluatedCount: number, requiredCount: number, failedDomains: string[]}}
|
|
280
|
+
*/
|
|
281
|
+
export function computeDomainCoverage(domainStatuses, requiredDomains = REQUIRED_DOMAINS) {
|
|
282
|
+
const failedDomains = [];
|
|
283
|
+
let evaluatedCount = 0;
|
|
284
|
+
|
|
285
|
+
for (const domain of requiredDomains) {
|
|
286
|
+
const status = domainStatuses[domain];
|
|
287
|
+
if (status && isDomainEvaluated(status)) {
|
|
288
|
+
evaluatedCount++;
|
|
289
|
+
} else {
|
|
290
|
+
failedDomains.push(domain);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const requiredCount = requiredDomains.length;
|
|
295
|
+
const coverage = requiredCount > 0 ? evaluatedCount / requiredCount : 1;
|
|
296
|
+
|
|
297
|
+
return { coverage, evaluatedCount, requiredCount, failedDomains };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ---------------------------------------------------------------------------
|
|
301
|
+
// Canonical completeness
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* @typedef {object} DomainStatus
|
|
306
|
+
* @property {string} status One of EVALUATION_STATUS values.
|
|
307
|
+
* @property {boolean} evaluated Whether evaluation was performed.
|
|
308
|
+
* @property {boolean} partial Whether evaluation was partial.
|
|
309
|
+
* @property {boolean} notEvaluated Whether evaluation was not performed.
|
|
310
|
+
* @property {boolean} unsupported Whether the domain is unsupported.
|
|
311
|
+
* @property {boolean} refused Whether evaluation was refused.
|
|
312
|
+
* @property {string} note Explanatory note about the status.
|
|
313
|
+
*/
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* @typedef {object} CompletenessResult
|
|
317
|
+
* @property {object} domains Domain-level statuses, keyed by domain name.
|
|
318
|
+
* @property {DomainStatus} domains.structural
|
|
319
|
+
* @property {DomainStatus} domains.constraint
|
|
320
|
+
* @property {DomainStatus} domains.boundary
|
|
321
|
+
* @property {DomainStatus} domains.decision
|
|
322
|
+
* @property {DomainStatus} domains.findings
|
|
323
|
+
* @property {DomainStatus} domains.debt
|
|
324
|
+
* @property {DomainStatus} domains.governance
|
|
325
|
+
* @property {DomainStatus} domains.evidence
|
|
326
|
+
* @property {boolean} overallComplete True only when ALL applicable domains are EVALUATED
|
|
327
|
+
* AND the Evidence-Complete contract is satisfied.
|
|
328
|
+
* @property {string} overallStatus One of EVALUATION_STATUS values.
|
|
329
|
+
* @property {EvidenceCompleteContract} [evidenceComplete] The Evidence-Complete contract,
|
|
330
|
+
* present when computed.
|
|
331
|
+
* @property {number} [hiddenGapCount] Number of domains NOT_EVALUATED without a note.
|
|
332
|
+
* @property {number} [falseCompleteCount] Number of false-complete detections.
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Builds canonical completeness from domain statuses.
|
|
336
|
+
* Now includes 8 domains (structural, constraint, boundary, decision,
|
|
337
|
+
* findings, debt, governance, evidence).
|
|
338
|
+
*
|
|
339
|
+
* @param {object} [input]
|
|
340
|
+
* @param {DomainStatus} [input.structural] The structural domain status.
|
|
341
|
+
* @param {DomainStatus} [input.constraint] The constraint domain status.
|
|
342
|
+
* @param {DomainStatus} [input.boundary] The boundary domain status.
|
|
343
|
+
* @param {DomainStatus} [input.decision] The decision domain status.
|
|
344
|
+
* @param {DomainStatus} [input.findings] The findings domain status.
|
|
345
|
+
* @param {DomainStatus} [input.debt] The debt domain status.
|
|
346
|
+
* @param {DomainStatus} [input.governance] The governance domain status.
|
|
347
|
+
* @param {DomainStatus} [input.evidence] The evidence domain status.
|
|
348
|
+
* @param {EvidenceCompleteContract} [input.evidenceComplete] Optional Evidence-Complete contract.
|
|
349
|
+
* @returns {CompletenessResult}
|
|
350
|
+
*/
|
|
351
|
+
export function buildCompleteness({
|
|
352
|
+
structural,
|
|
353
|
+
constraint,
|
|
354
|
+
boundary,
|
|
355
|
+
decision,
|
|
356
|
+
findings,
|
|
357
|
+
debt,
|
|
358
|
+
governance,
|
|
359
|
+
evidence,
|
|
360
|
+
evidenceComplete,
|
|
361
|
+
} = {}) {
|
|
362
|
+
const domains = {
|
|
363
|
+
structural: structural ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED),
|
|
364
|
+
constraint: constraint ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED),
|
|
365
|
+
boundary: boundary ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED),
|
|
366
|
+
decision: decision ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED),
|
|
367
|
+
findings: findings ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED),
|
|
368
|
+
debt: debt ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED),
|
|
369
|
+
governance: governance ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED),
|
|
370
|
+
evidence: evidence ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED),
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const statuses = Object.values(domains).map((d) => d.status);
|
|
374
|
+
const domainOverallComplete = statuses.every((s) => s === EVALUATION_STATUS.EVALUATED);
|
|
375
|
+
|
|
376
|
+
// If an Evidence-Complete contract is provided, enforce it as a gate.
|
|
377
|
+
// When no contract is provided, overallComplete MUST be false — the
|
|
378
|
+
// evaluation has not proven its evidence gates.
|
|
379
|
+
let ecComplete = false;
|
|
380
|
+
let falseCompleteCount = 0;
|
|
381
|
+
if (evidenceComplete) {
|
|
382
|
+
ecComplete = evidenceComplete.overallComplete;
|
|
383
|
+
falseCompleteCount = evidenceComplete.falseCompleteCount;
|
|
384
|
+
// Detect false complete: domain claims complete but Evidence-Complete fails
|
|
385
|
+
if (domainOverallComplete && !ecComplete) {
|
|
386
|
+
falseCompleteCount++;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Detect hidden gaps: a domain is NOT_EVALUATED but no note explains why
|
|
391
|
+
let hiddenGapCount = 0;
|
|
392
|
+
for (const [, domain] of Object.entries(domains)) {
|
|
393
|
+
if (domain.status === EVALUATION_STATUS.NOT_EVALUATED && !domain.note) {
|
|
394
|
+
hiddenGapCount++;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const overallComplete = domainOverallComplete && ecComplete;
|
|
399
|
+
const overallStatus = worstStatus(...statuses, ecComplete ? EVALUATED : NOT_EVALUATED);
|
|
400
|
+
|
|
401
|
+
return {
|
|
402
|
+
domains,
|
|
403
|
+
overallComplete,
|
|
404
|
+
overallStatus,
|
|
405
|
+
...(evidenceComplete ? { evidenceComplete: { ...evidenceComplete, falseCompleteCount } } : {}),
|
|
406
|
+
...(hiddenGapCount > 0 ? { hiddenGapCount } : {}),
|
|
407
|
+
...(falseCompleteCount > 0 ? { falseCompleteCount } : {}),
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// ---------------------------------------------------------------------------
|
|
412
|
+
// Evidence-Complete gate
|
|
413
|
+
// ---------------------------------------------------------------------------
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Builds an Evidence-Complete contract from the individual gate values.
|
|
417
|
+
*
|
|
418
|
+
* Only gates required for the given `contractType` are considered for
|
|
419
|
+
* `overallComplete`. Gates not required are still tracked and reported
|
|
420
|
+
* but do NOT block completeness.
|
|
421
|
+
*
|
|
422
|
+
* @param {object} gates
|
|
423
|
+
* @param {number} [gates.domainCoverage] Ratio (0-1).
|
|
424
|
+
* @param {number} [gates.claimEvidenceCoverage] Ratio (0-1).
|
|
425
|
+
* @param {number} [gates.causalCoverage] Ratio (0-1).
|
|
426
|
+
* @param {number} [gates.provenanceCoverage] Ratio (0-1).
|
|
427
|
+
* @param {number} [gates.mutationCoverage] Ratio (0-1).
|
|
428
|
+
* @param {number} [gates.surfaceParity] Ratio (0-1).
|
|
429
|
+
* @param {number} [gates.hiddenGapCount] Count (0 = pass).
|
|
430
|
+
* @param {number} [gates.falseCompleteCount] Count (0 = pass).
|
|
431
|
+
* @param {boolean} [gates.baseIdentityValid] Boolean (true = pass).
|
|
432
|
+
* @param {boolean} [gates.deterministic] Boolean (true = pass).
|
|
433
|
+
* @param {string} [gates.contractType] Evaluation contract type for gate
|
|
434
|
+
* requirements (defaults to SCENARIO, the most restrictive).
|
|
435
|
+
* @returns {EvidenceCompleteContract}
|
|
436
|
+
*/
|
|
437
|
+
export function buildEvidenceComplete({
|
|
438
|
+
domainCoverage = 0,
|
|
439
|
+
claimEvidenceCoverage = 0,
|
|
440
|
+
causalCoverage = 0,
|
|
441
|
+
provenanceCoverage = 0,
|
|
442
|
+
mutationCoverage = 0,
|
|
443
|
+
surfaceParity = 0,
|
|
444
|
+
hiddenGapCount = -1,
|
|
445
|
+
falseCompleteCount = -1,
|
|
446
|
+
baseIdentityValid = false,
|
|
447
|
+
deterministic = false,
|
|
448
|
+
contractType = EVALUATION_CONTRACT_TYPES.SCENARIO,
|
|
449
|
+
} = {}) {
|
|
450
|
+
const rawGates = {
|
|
451
|
+
domainCoverage: { value: domainCoverage, pass: domainCoverage === 1 },
|
|
452
|
+
claimEvidenceCoverage: { value: claimEvidenceCoverage, pass: claimEvidenceCoverage === 1 },
|
|
453
|
+
causalCoverage: { value: causalCoverage, pass: causalCoverage === 1 },
|
|
454
|
+
provenanceCoverage: { value: provenanceCoverage, pass: provenanceCoverage === 1 },
|
|
455
|
+
mutationCoverage: { value: mutationCoverage, pass: mutationCoverage === 1 },
|
|
456
|
+
surfaceParity: { value: surfaceParity, pass: surfaceParity === 1 },
|
|
457
|
+
hiddenGapCount: { value: hiddenGapCount, pass: hiddenGapCount === 0 },
|
|
458
|
+
falseCompleteCount: { value: falseCompleteCount, pass: falseCompleteCount === 0 },
|
|
459
|
+
baseIdentityValid: { value: baseIdentityValid, pass: baseIdentityValid === true },
|
|
460
|
+
deterministic: { value: deterministic, pass: deterministic === true },
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
// Only required gates block overallComplete
|
|
464
|
+
const allRequiredPass = Object.keys(rawGates)
|
|
465
|
+
.filter((key) => isGateRequired(key, contractType))
|
|
466
|
+
.every((key) => rawGates[key].pass);
|
|
467
|
+
// Annotate each gate with whether it is required for this contract type
|
|
468
|
+
/** @type {any} */
|
|
469
|
+
const gates = Object.fromEntries(
|
|
470
|
+
Object.entries(rawGates).map(([key, gate]) => [
|
|
471
|
+
key,
|
|
472
|
+
{ ...gate, required: isGateRequired(key, contractType) },
|
|
473
|
+
]),
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
return {
|
|
477
|
+
contractType,
|
|
478
|
+
domainCoverage,
|
|
479
|
+
claimEvidenceCoverage,
|
|
480
|
+
causalCoverage,
|
|
481
|
+
provenanceCoverage,
|
|
482
|
+
mutationCoverage,
|
|
483
|
+
surfaceParity,
|
|
484
|
+
hiddenGapCount,
|
|
485
|
+
falseCompleteCount,
|
|
486
|
+
baseIdentityValid,
|
|
487
|
+
deterministic,
|
|
488
|
+
overallStatus: allRequiredPass ? "complete" : "incomplete",
|
|
489
|
+
overallComplete: allRequiredPass,
|
|
490
|
+
gates,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Asserts that the Evidence-Complete contract is satisfied.
|
|
496
|
+
* Throws with a detailed message listing every failing gate.
|
|
497
|
+
*
|
|
498
|
+
* @param {EvidenceCompleteContract} ec The Evidence-Complete contract to verify.
|
|
499
|
+
* @returns {void}
|
|
500
|
+
* @throws {Error} When any gate fails.
|
|
501
|
+
*/
|
|
502
|
+
export function assertEvidenceComplete(ec) {
|
|
503
|
+
if (ec.overallComplete) return;
|
|
504
|
+
|
|
505
|
+
const contractType = ec.contractType || EVALUATION_CONTRACT_TYPES.SCENARIO;
|
|
506
|
+
const failures = [];
|
|
507
|
+
for (const gate of EVIDENCE_COMPLETE_GATES) {
|
|
508
|
+
const g = ec.gates[gate.key];
|
|
509
|
+
// Skip non-required gates for this contract type
|
|
510
|
+
if (g.required === false) continue;
|
|
511
|
+
if (!g.pass) {
|
|
512
|
+
failures.push(`${gate.label}: ${JSON.stringify(g.value)} (expected pass)`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (failures.length === 0) return;
|
|
517
|
+
|
|
518
|
+
throw new Error(
|
|
519
|
+
`Evidence-Complete contract not satisfied.\n` +
|
|
520
|
+
` Contract type: ${contractType}\n` +
|
|
521
|
+
` Overall: ${ec.overallStatus}\n` +
|
|
522
|
+
` Failed gates:\n ${failures.join("\n ")}`,
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// ---------------------------------------------------------------------------
|
|
527
|
+
// Governance completeness
|
|
528
|
+
// ---------------------------------------------------------------------------
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* @typedef {object} GovernanceCompletenessResult
|
|
532
|
+
* @property {DomainStatus} domain The governance domain status.
|
|
533
|
+
* @property {DomainStatus} findings The findings domain status.
|
|
534
|
+
* @property {DomainStatus} debt The debt domain status.
|
|
535
|
+
* @property {string} findingsStatus EVALUATION_STATUS for findings.
|
|
536
|
+
* @property {string} debtStatus EVALUATION_STATUS for debt.
|
|
537
|
+
* @property {number} findingsCount Number of findings.
|
|
538
|
+
* @property {number} debtCount Number of debt entries.
|
|
539
|
+
*/
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Builds governance completeness from findings and debt statuses.
|
|
543
|
+
*
|
|
544
|
+
* @param {object} [input]
|
|
545
|
+
* @param {string} [input.findingsStatus] EVALUATION_STATUS for findings.
|
|
546
|
+
* @param {string} [input.debtStatus] EVALUATION_STATUS for debt.
|
|
547
|
+
* @param {number} [input.findingsCount] Number of findings.
|
|
548
|
+
* @param {number} [input.debtCount] Number of debt entries.
|
|
549
|
+
* @returns {GovernanceCompletenessResult}
|
|
550
|
+
*/
|
|
551
|
+
export function buildGovernanceCompleteness({
|
|
552
|
+
findingsStatus = EVALUATION_STATUS.NOT_EVALUATED,
|
|
553
|
+
debtStatus = EVALUATION_STATUS.NOT_EVALUATED,
|
|
554
|
+
findingsCount = 0,
|
|
555
|
+
debtCount = 0,
|
|
556
|
+
} = {}) {
|
|
557
|
+
const findingsDomain = createDomain(findingsStatus);
|
|
558
|
+
const debtDomain = createDomain(debtStatus);
|
|
559
|
+
|
|
560
|
+
const status = worstStatus(findingsStatus, debtStatus);
|
|
561
|
+
|
|
562
|
+
let note = "";
|
|
563
|
+
if (status !== EVALUATION_STATUS.EVALUATED) {
|
|
564
|
+
const parts = [];
|
|
565
|
+
if (findingsStatus !== EVALUATION_STATUS.EVALUATED) {
|
|
566
|
+
parts.push(`findings: ${findingsStatus}`);
|
|
567
|
+
}
|
|
568
|
+
if (debtStatus !== EVALUATION_STATUS.EVALUATED) {
|
|
569
|
+
parts.push(`debt: ${debtStatus}`);
|
|
570
|
+
}
|
|
571
|
+
note = `Governance incomplete — ${parts.join(", ")}`;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return {
|
|
575
|
+
domain: createDomain(status, note),
|
|
576
|
+
findings: findingsDomain,
|
|
577
|
+
debt: debtDomain,
|
|
578
|
+
findingsStatus,
|
|
579
|
+
debtStatus,
|
|
580
|
+
findingsCount,
|
|
581
|
+
debtCount,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// ---------------------------------------------------------------------------
|
|
586
|
+
// Scenario completeness
|
|
587
|
+
// ---------------------------------------------------------------------------
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* @typedef {object} ScenarioCompletenessResult
|
|
591
|
+
* @property {object} domains Domain-level statuses (standard 5 domains).
|
|
592
|
+
* @property {boolean} overallComplete True only when ALL domains are EVALUATED,
|
|
593
|
+
* including scenario-specific domains.
|
|
594
|
+
* @property {string} overallStatus One of EVALUATION_STATUS values.
|
|
595
|
+
* @property {object} scenarioDomains Scenario-specific domain statuses.
|
|
596
|
+
* @property {DomainStatus} scenarioDomains.changes Whether all changes were applied.
|
|
597
|
+
* @property {DomainStatus} scenarioDomains.base Whether the base revision identity was verified.
|
|
598
|
+
* @property {DomainStatus} scenarioDomains.mutationCoverage Mutation coverage status.
|
|
599
|
+
* @property {EvidenceCompleteContract} [evidenceComplete] The Evidence-Complete contract.
|
|
600
|
+
*/
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Builds scenario completeness, extending canonical completeness with
|
|
604
|
+
* scenario-specific domains (changes, base identity, mutation coverage).
|
|
605
|
+
*
|
|
606
|
+
* In a scenario context, standard domains (structural, constraint, boundary,
|
|
607
|
+
* decision) pass through from the evaluation — they are NOT automatically
|
|
608
|
+
* EVALUATED. The scenario-specific domains capture whether changes were
|
|
609
|
+
* fully applied, whether the base revision identity was verified, and
|
|
610
|
+
* whether mutation coverage was complete.
|
|
611
|
+
*
|
|
612
|
+
* @param {object} [input]
|
|
613
|
+
* @param {boolean} [input.changesComplete] Whether all scenario changes were applied.
|
|
614
|
+
* @param {boolean} [input.baseIdentityVerified] Whether the base revision identity was verified.
|
|
615
|
+
* @param {boolean} [input.mutationCoverageComplete] Whether all mutations have explicit outcomes.
|
|
616
|
+
* @param {GovernanceCompletenessResult} [input.governance] Governance completeness
|
|
617
|
+
* from buildGovernanceCompleteness.
|
|
618
|
+
* @param {EvidenceCompleteContract} [input.evidenceComplete] Optional Evidence-Complete contract.
|
|
619
|
+
* @param {object} [input.domains] Existing domain statuses to pass through.
|
|
620
|
+
* @returns {ScenarioCompletenessResult}
|
|
621
|
+
*/
|
|
622
|
+
export function buildScenarioCompleteness({
|
|
623
|
+
changesComplete = true,
|
|
624
|
+
baseIdentityVerified = true,
|
|
625
|
+
mutationCoverageComplete = true,
|
|
626
|
+
governance,
|
|
627
|
+
evidenceComplete,
|
|
628
|
+
domains: existingDomains,
|
|
629
|
+
} = {}) {
|
|
630
|
+
const changesDomain = changesComplete
|
|
631
|
+
? createDomain(EVALUATION_STATUS.EVALUATED)
|
|
632
|
+
: createDomain(EVALUATION_STATUS.PARTIAL, "Some changes could not be applied");
|
|
633
|
+
|
|
634
|
+
const baseDomain = baseIdentityVerified
|
|
635
|
+
? createDomain(EVALUATION_STATUS.EVALUATED)
|
|
636
|
+
: createDomain(EVALUATION_STATUS.NOT_EVALUATED, "Base revision identity could not be verified");
|
|
637
|
+
|
|
638
|
+
const mutationDomain = mutationCoverageComplete
|
|
639
|
+
? createDomain(EVALUATION_STATUS.EVALUATED)
|
|
640
|
+
: createDomain(EVALUATION_STATUS.PARTIAL, "Not all mutations have explicit outcomes");
|
|
641
|
+
|
|
642
|
+
const governanceDomain =
|
|
643
|
+
governance?.domain ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED, "Governance not evaluated");
|
|
644
|
+
|
|
645
|
+
// Use existing domains if provided, otherwise create defaults
|
|
646
|
+
const structuralDomain =
|
|
647
|
+
existingDomains?.structural ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED);
|
|
648
|
+
const constraintDomain =
|
|
649
|
+
existingDomains?.constraint ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED);
|
|
650
|
+
const boundaryDomain = existingDomains?.boundary ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED);
|
|
651
|
+
const decisionDomain = existingDomains?.decision ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED);
|
|
652
|
+
const findingsDomain = existingDomains?.findings ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED);
|
|
653
|
+
const debtDomain = existingDomains?.debt ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED);
|
|
654
|
+
const evidenceDomain = existingDomains?.evidence ?? createDomain(EVALUATION_STATUS.NOT_EVALUATED);
|
|
655
|
+
|
|
656
|
+
const baseResult = buildCompleteness({
|
|
657
|
+
structural: structuralDomain,
|
|
658
|
+
constraint: constraintDomain,
|
|
659
|
+
boundary: boundaryDomain,
|
|
660
|
+
decision: decisionDomain,
|
|
661
|
+
findings: findingsDomain,
|
|
662
|
+
debt: debtDomain,
|
|
663
|
+
governance: governanceDomain,
|
|
664
|
+
evidence: evidenceDomain,
|
|
665
|
+
evidenceComplete,
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
// Recompute overall: base domains + EC gate + scenario domains.
|
|
669
|
+
// baseResult.overallComplete already includes the evidenceComplete gate,
|
|
670
|
+
// so reusing it prevents the silent-complete defect.
|
|
671
|
+
const overallComplete =
|
|
672
|
+
baseResult.overallComplete &&
|
|
673
|
+
changesDomain.status === EVALUATION_STATUS.EVALUATED &&
|
|
674
|
+
baseDomain.status === EVALUATION_STATUS.EVALUATED &&
|
|
675
|
+
mutationDomain.status === EVALUATION_STATUS.EVALUATED;
|
|
676
|
+
const allStatuses = [
|
|
677
|
+
...Object.values(baseResult.domains).map((d) => d.status),
|
|
678
|
+
changesDomain.status,
|
|
679
|
+
baseDomain.status,
|
|
680
|
+
mutationDomain.status,
|
|
681
|
+
];
|
|
682
|
+
// Include EC gate status in overall status: when evidenceComplete is
|
|
683
|
+
// provided and fails, overall status must reflect that.
|
|
684
|
+
const ecStatus = evidenceComplete
|
|
685
|
+
? evidenceComplete.overallComplete
|
|
686
|
+
? EVALUATION_STATUS.EVALUATED
|
|
687
|
+
: EVALUATION_STATUS.NOT_EVALUATED
|
|
688
|
+
: EVALUATION_STATUS.NOT_EVALUATED;
|
|
689
|
+
const overallStatus = worstStatus(...allStatuses, ecStatus);
|
|
690
|
+
|
|
691
|
+
return {
|
|
692
|
+
domains: baseResult.domains,
|
|
693
|
+
overallComplete,
|
|
694
|
+
overallStatus,
|
|
695
|
+
scenarioDomains: {
|
|
696
|
+
changes: changesDomain,
|
|
697
|
+
base: baseDomain,
|
|
698
|
+
mutationCoverage: mutationDomain,
|
|
699
|
+
},
|
|
700
|
+
...(baseResult.evidenceComplete ? { evidenceComplete: baseResult.evidenceComplete } : {}),
|
|
701
|
+
...(baseResult.hiddenGapCount !== undefined
|
|
702
|
+
? { hiddenGapCount: baseResult.hiddenGapCount }
|
|
703
|
+
: {}),
|
|
704
|
+
...(baseResult.falseCompleteCount !== undefined
|
|
705
|
+
? { falseCompleteCount: baseResult.falseCompleteCount }
|
|
706
|
+
: {}),
|
|
707
|
+
};
|
|
708
|
+
}
|