mustflow 2.22.5 → 2.22.9
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/README.md +8 -0
- package/dist/cli/commands/classify.js +2 -0
- package/dist/cli/commands/dashboard.js +9 -69
- package/dist/cli/commands/run/receipt.js +1 -0
- package/dist/cli/commands/run.js +14 -1
- package/dist/cli/commands/verify/evidence-input.js +269 -0
- package/dist/cli/commands/verify/input.js +212 -0
- package/dist/cli/commands/verify.js +23 -482
- package/dist/cli/i18n/en.js +3 -0
- package/dist/cli/i18n/es.js +3 -0
- package/dist/cli/i18n/fr.js +3 -0
- package/dist/cli/i18n/hi.js +3 -0
- package/dist/cli/i18n/ko.js +3 -0
- package/dist/cli/i18n/zh.js +3 -0
- package/dist/cli/lib/dashboard-export.js +2 -0
- package/dist/cli/lib/dashboard-mutations.js +79 -0
- package/dist/cli/lib/local-index/command-effect-index.js +25 -0
- package/dist/cli/lib/local-index/hashing.js +7 -0
- package/dist/cli/lib/local-index/index.js +127 -826
- package/dist/cli/lib/local-index/source-index.js +137 -0
- package/dist/cli/lib/local-index/verification-evidence.js +451 -0
- package/dist/cli/lib/local-index/workflow-documents.js +204 -0
- package/dist/cli/lib/run-root-trust.js +27 -0
- package/dist/core/change-classification-policy.js +47 -0
- package/dist/core/change-classification.js +10 -43
- package/dist/core/contract-lint.js +6 -2
- package/dist/core/correlation-id.js +16 -0
- package/dist/core/run-receipt.js +1 -0
- package/package.json +4 -1
- package/schemas/README.md +4 -0
- package/schemas/change-verification-report.schema.json +4 -0
- package/schemas/classify-report.schema.json +4 -0
- package/schemas/dashboard-export.schema.json +4 -0
- package/schemas/latest-run-pointer.schema.json +4 -0
- package/schemas/run-receipt.schema.json +4 -0
- package/schemas/verify-report.schema.json +4 -0
- package/schemas/verify-run-manifest.schema.json +4 -0
- package/templates/default/i18n.toml +3 -3
- package/templates/default/locales/en/.mustflow/skills/architecture-deepening-review/SKILL.md +25 -2
- package/templates/default/locales/en/.mustflow/skills/security-privacy-review/SKILL.md +9 -1
- package/templates/default/locales/en/.mustflow/skills/test-design-guard/SKILL.md +9 -1
- package/templates/default/manifest.toml +1 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { isRecord, readStringArray } from '../command-contract.js';
|
|
4
|
+
import { readMustflowTomlFile } from '../toml.js';
|
|
5
|
+
import { listSourceAnchorFiles } from '../../../core/source-anchors.js';
|
|
6
|
+
import { INDEX_CONFIG_RELATIVE_PATH, LATEST_RUN_STATE_RELATIVE_PATH, MUSTFLOW_RELATIVE_PATH, SOURCE_INDEX_MAX_FILE_BYTES, } from './constants.js';
|
|
7
|
+
import { sha256Bytes, sha256Text } from './hashing.js';
|
|
8
|
+
import { getExistingIndexablePaths } from './workflow-documents.js';
|
|
9
|
+
function readIndexToml(projectRoot) {
|
|
10
|
+
const indexConfigPath = path.join(projectRoot, ...INDEX_CONFIG_RELATIVE_PATH.split('/'));
|
|
11
|
+
if (!existsSync(indexConfigPath)) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
const parsed = readMustflowTomlFile(projectRoot, INDEX_CONFIG_RELATIVE_PATH);
|
|
15
|
+
return isRecord(parsed) ? parsed : undefined;
|
|
16
|
+
}
|
|
17
|
+
function readNestedTable(table, key) {
|
|
18
|
+
if (!table || !isRecord(table[key])) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
return table[key];
|
|
22
|
+
}
|
|
23
|
+
function readOptionalStringArray(table, key) {
|
|
24
|
+
return table ? readStringArray(table, key) ?? null : null;
|
|
25
|
+
}
|
|
26
|
+
function readBoolean(table, key) {
|
|
27
|
+
const value = table?.[key];
|
|
28
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
29
|
+
}
|
|
30
|
+
function readPositiveInteger(table, key) {
|
|
31
|
+
const value = table?.[key];
|
|
32
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value <= 0) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
export function readMustflowToml(projectRoot) {
|
|
38
|
+
const mustflowPath = path.join(projectRoot, ...MUSTFLOW_RELATIVE_PATH.split('/'));
|
|
39
|
+
if (!existsSync(mustflowPath)) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
const parsed = readMustflowTomlFile(projectRoot, MUSTFLOW_RELATIVE_PATH);
|
|
43
|
+
return isRecord(parsed) ? parsed : undefined;
|
|
44
|
+
}
|
|
45
|
+
export function readLocalIndexSourceConfig(projectRoot) {
|
|
46
|
+
const sourceIndexTable = readNestedTable(readIndexToml(projectRoot), 'source_index');
|
|
47
|
+
const configuredMaxFileBytes = readPositiveInteger(sourceIndexTable, 'max_file_bytes');
|
|
48
|
+
return {
|
|
49
|
+
enabledByDefault: readBoolean(sourceIndexTable, 'enabled_by_default') === true,
|
|
50
|
+
include: readOptionalStringArray(sourceIndexTable, 'include') ?? [],
|
|
51
|
+
exclude: readOptionalStringArray(sourceIndexTable, 'exclude') ?? [],
|
|
52
|
+
maxFileBytes: Math.min(configuredMaxFileBytes ?? SOURCE_INDEX_MAX_FILE_BYTES, SOURCE_INDEX_MAX_FILE_BYTES),
|
|
53
|
+
allowedExtensions: readOptionalStringArray(sourceIndexTable, 'allowed_extensions') ?? [],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function getSourceScopeHash(includeSource, sourceConfig) {
|
|
57
|
+
return sha256Text(JSON.stringify({
|
|
58
|
+
includeSource,
|
|
59
|
+
sourceConfig,
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
export function normalizeIndexedFileSourceScope(value) {
|
|
63
|
+
if (value === 'source_anchor' || value === 'state') {
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
return 'workflow';
|
|
67
|
+
}
|
|
68
|
+
export function readIndexedFileRecord(projectRoot, relativePath, sourceScope, contentHash = null) {
|
|
69
|
+
const metadata = readIndexedFileMetadataRecord(projectRoot, relativePath, sourceScope);
|
|
70
|
+
return {
|
|
71
|
+
...metadata,
|
|
72
|
+
contentHash: contentHash ?? sha256Bytes(readFileSync(path.join(projectRoot, ...relativePath.split('/')))),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function hashIndexedFileMetadataRecord(projectRoot, metadata) {
|
|
76
|
+
return {
|
|
77
|
+
...metadata,
|
|
78
|
+
contentHash: sha256Bytes(readFileSync(path.join(projectRoot, ...metadata.path.split('/')))),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export function hashIndexedFileMetadataRecords(projectRoot, metadataRecords) {
|
|
82
|
+
return metadataRecords.map((metadata) => hashIndexedFileMetadataRecord(projectRoot, metadata));
|
|
83
|
+
}
|
|
84
|
+
export function readIndexedFileMetadataRecord(projectRoot, relativePath, sourceScope) {
|
|
85
|
+
const fullPath = path.join(projectRoot, ...relativePath.split('/'));
|
|
86
|
+
const stats = statSync(fullPath);
|
|
87
|
+
return {
|
|
88
|
+
path: relativePath,
|
|
89
|
+
sourceScope,
|
|
90
|
+
sizeBytes: stats.size,
|
|
91
|
+
mtimeMs: Math.round(stats.mtimeMs),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export function collectIndexedFileRecords(projectRoot, documents, sourceAnchors, sourceAnchorCandidatePaths = []) {
|
|
95
|
+
const records = new Map();
|
|
96
|
+
for (const document of documents) {
|
|
97
|
+
records.set(document.path, readIndexedFileRecord(projectRoot, document.path, 'workflow', document.contentHash));
|
|
98
|
+
}
|
|
99
|
+
const sourcePaths = new Set([...sourceAnchorCandidatePaths, ...sourceAnchors.map((anchor) => anchor.path)]);
|
|
100
|
+
for (const anchorPath of [...sourcePaths].sort((left, right) => left.localeCompare(right))) {
|
|
101
|
+
if (!records.has(anchorPath)) {
|
|
102
|
+
records.set(anchorPath, readIndexedFileRecord(projectRoot, anchorPath, 'source_anchor'));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (existsSync(path.join(projectRoot, ...LATEST_RUN_STATE_RELATIVE_PATH.split('/')))) {
|
|
106
|
+
records.set(LATEST_RUN_STATE_RELATIVE_PATH, readIndexedFileRecord(projectRoot, LATEST_RUN_STATE_RELATIVE_PATH, 'state'));
|
|
107
|
+
}
|
|
108
|
+
return [...records.values()].sort((left, right) => left.path.localeCompare(right.path));
|
|
109
|
+
}
|
|
110
|
+
export function collectSourceAnchorCandidatePaths(projectRoot, sourceConfig) {
|
|
111
|
+
return listSourceAnchorFiles(projectRoot, {
|
|
112
|
+
...sourceConfig,
|
|
113
|
+
excludeGeneratedOrVendor: true,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
export function collectFastPreflightIndexedFileMetadataRecords(projectRoot, includeSource, sourceConfig) {
|
|
117
|
+
const records = new Map();
|
|
118
|
+
for (const relativePath of getExistingIndexablePaths(projectRoot)) {
|
|
119
|
+
records.set(relativePath, readIndexedFileMetadataRecord(projectRoot, relativePath, 'workflow'));
|
|
120
|
+
}
|
|
121
|
+
if (includeSource) {
|
|
122
|
+
try {
|
|
123
|
+
for (const sourcePath of collectSourceAnchorCandidatePaths(projectRoot, sourceConfig)) {
|
|
124
|
+
if (!records.has(sourcePath)) {
|
|
125
|
+
records.set(sourcePath, readIndexedFileMetadataRecord(projectRoot, sourcePath, 'source_anchor'));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (existsSync(path.join(projectRoot, ...LATEST_RUN_STATE_RELATIVE_PATH.split('/')))) {
|
|
134
|
+
records.set(LATEST_RUN_STATE_RELATIVE_PATH, readIndexedFileMetadataRecord(projectRoot, LATEST_RUN_STATE_RELATIVE_PATH, 'state'));
|
|
135
|
+
}
|
|
136
|
+
return [...records.values()].sort((left, right) => left.path.localeCompare(right.path));
|
|
137
|
+
}
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { MUSTFLOW_JSON_MAX_BYTES, readMustflowTextFile } from '../mustflow-read.js';
|
|
4
|
+
import { LATEST_RUN_STATE_RELATIVE_PATH } from './constants.js';
|
|
5
|
+
import { sha256Bytes, sha256Text } from './hashing.js';
|
|
6
|
+
const VALIDATION_RATCHET_RISK_CODES = new Set([
|
|
7
|
+
'related_test_deleted',
|
|
8
|
+
'skip_or_only_marker_present',
|
|
9
|
+
'todo_or_pending_marker_added',
|
|
10
|
+
'assertion_count_decreased',
|
|
11
|
+
'assertion_matcher_weakened',
|
|
12
|
+
'negative_assertion_removed',
|
|
13
|
+
'exception_assertion_removed',
|
|
14
|
+
'snapshot_mass_updated',
|
|
15
|
+
'golden_output_replaced',
|
|
16
|
+
'verification_intent_disabled',
|
|
17
|
+
'verification_required_after_removed',
|
|
18
|
+
'success_exit_codes_widened',
|
|
19
|
+
'command_allows_no_tests',
|
|
20
|
+
'command_forces_snapshot_update',
|
|
21
|
+
'command_hides_failure',
|
|
22
|
+
'coverage_threshold_lowered',
|
|
23
|
+
'test_selection_narrowed',
|
|
24
|
+
]);
|
|
25
|
+
function emptyVerificationEvidenceIndex() {
|
|
26
|
+
return {
|
|
27
|
+
summaries: [],
|
|
28
|
+
verificationPlans: [],
|
|
29
|
+
acceptanceCriteria: [],
|
|
30
|
+
criterionCoverage: [],
|
|
31
|
+
receipts: [],
|
|
32
|
+
commandReceiptSummaries: [],
|
|
33
|
+
coverageStates: [],
|
|
34
|
+
riskSignals: [],
|
|
35
|
+
validationRatchetSignals: [],
|
|
36
|
+
completionVerdictSummaries: [],
|
|
37
|
+
failureFingerprints: [],
|
|
38
|
+
reproRoutes: [],
|
|
39
|
+
reproObservations: [],
|
|
40
|
+
failureFingerprintReadModels: [],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function isJsonRecord(value) {
|
|
44
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
45
|
+
}
|
|
46
|
+
function readJsonRecord(projectRoot, relativePath) {
|
|
47
|
+
try {
|
|
48
|
+
const content = readMustflowTextFile(projectRoot, relativePath, { maxBytes: MUSTFLOW_JSON_MAX_BYTES });
|
|
49
|
+
const parsed = JSON.parse(content);
|
|
50
|
+
return isJsonRecord(parsed) ? { content, value: parsed } : null;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function stringField(record, key) {
|
|
57
|
+
const value = record?.[key];
|
|
58
|
+
return typeof value === 'string' ? value : null;
|
|
59
|
+
}
|
|
60
|
+
function booleanField(record, key) {
|
|
61
|
+
return record?.[key] === true;
|
|
62
|
+
}
|
|
63
|
+
function numberField(record, key) {
|
|
64
|
+
const value = record?.[key];
|
|
65
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
66
|
+
}
|
|
67
|
+
function recordField(record, key) {
|
|
68
|
+
const value = record?.[key];
|
|
69
|
+
return isJsonRecord(value) ? value : null;
|
|
70
|
+
}
|
|
71
|
+
function recordArrayField(record, key) {
|
|
72
|
+
const value = record?.[key];
|
|
73
|
+
return Array.isArray(value) ? value.filter(isJsonRecord) : [];
|
|
74
|
+
}
|
|
75
|
+
function stringArrayField(record, key) {
|
|
76
|
+
const value = record?.[key];
|
|
77
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === 'string') : [];
|
|
78
|
+
}
|
|
79
|
+
function hashJson(value) {
|
|
80
|
+
return sha256Text(JSON.stringify(value));
|
|
81
|
+
}
|
|
82
|
+
function stringListHash(values) {
|
|
83
|
+
const normalized = values.filter((value) => typeof value === 'string' && value.length > 0);
|
|
84
|
+
return normalized.length > 0 ? hashJson([...normalized].sort((left, right) => left.localeCompare(right))) : null;
|
|
85
|
+
}
|
|
86
|
+
function reproObservation(routeId, phase, evidence) {
|
|
87
|
+
const status = stringField(evidence, 'status');
|
|
88
|
+
const outcome = stringField(evidence, 'outcome') ?? status;
|
|
89
|
+
const receiptHash = stringField(evidence, 'receipt_sha256');
|
|
90
|
+
const diagnosticFingerprint = stringField(evidence, 'diagnostic_fingerprint') ??
|
|
91
|
+
stringField(evidence, 'diagnostic_hash') ??
|
|
92
|
+
hashJson({
|
|
93
|
+
phase,
|
|
94
|
+
status,
|
|
95
|
+
outcome,
|
|
96
|
+
summary: stringField(evidence, 'summary'),
|
|
97
|
+
reason: stringField(evidence, 'reason'),
|
|
98
|
+
});
|
|
99
|
+
return {
|
|
100
|
+
routeId,
|
|
101
|
+
phase,
|
|
102
|
+
outcome,
|
|
103
|
+
receiptHash,
|
|
104
|
+
diagnosticFingerprint,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function evidenceStatusForRunReceipt(latest) {
|
|
108
|
+
return stringField(latest, 'status') ?? (booleanField(latest, 'timed_out') ? 'timed_out' : 'unknown');
|
|
109
|
+
}
|
|
110
|
+
function failedIntentsFromReceipts(receipts) {
|
|
111
|
+
return receipts
|
|
112
|
+
.filter((receipt) => ['failed', 'timed_out', 'start_failed'].includes(receipt.status))
|
|
113
|
+
.map((receipt) => receipt.intent)
|
|
114
|
+
.filter((intent) => typeof intent === 'string' && intent.length > 0)
|
|
115
|
+
.sort((left, right) => left.localeCompare(right));
|
|
116
|
+
}
|
|
117
|
+
function createFailureFingerprint(input) {
|
|
118
|
+
if (input.status === 'passed' ||
|
|
119
|
+
input.status === 'verified' ||
|
|
120
|
+
(input.failedIntents.length === 0 && input.riskCodes.length === 0 && input.timedOut !== true && !input.errorKind)) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
return sha256Text(JSON.stringify({
|
|
124
|
+
command: input.command,
|
|
125
|
+
status: input.status,
|
|
126
|
+
verificationPlanId: input.verificationPlanId,
|
|
127
|
+
primaryReason: input.primaryReason,
|
|
128
|
+
failedIntents: [...input.failedIntents].sort((left, right) => left.localeCompare(right)),
|
|
129
|
+
riskCodes: [...input.riskCodes].sort((left, right) => left.localeCompare(right)),
|
|
130
|
+
runIntent: input.runIntent ?? null,
|
|
131
|
+
timedOut: input.timedOut === true,
|
|
132
|
+
exitCodeClass: input.exitCodeClass ?? null,
|
|
133
|
+
errorKind: input.errorKind ?? null,
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
export function createVerificationEvidenceIndex(projectRoot) {
|
|
137
|
+
const latestPath = path.join(projectRoot, ...LATEST_RUN_STATE_RELATIVE_PATH.split('/'));
|
|
138
|
+
if (!existsSync(latestPath)) {
|
|
139
|
+
return emptyVerificationEvidenceIndex();
|
|
140
|
+
}
|
|
141
|
+
const latestRecord = readJsonRecord(projectRoot, LATEST_RUN_STATE_RELATIVE_PATH);
|
|
142
|
+
if (!latestRecord) {
|
|
143
|
+
return emptyVerificationEvidenceIndex();
|
|
144
|
+
}
|
|
145
|
+
const latest = latestRecord.value;
|
|
146
|
+
const sourceHash = sha256Bytes(Buffer.from(latestRecord.content));
|
|
147
|
+
const command = stringField(latest, 'command') ?? 'unknown';
|
|
148
|
+
const kind = stringField(latest, 'kind') ?? (command === 'verify' ? 'verify_run_summary' : 'run_receipt');
|
|
149
|
+
const evidenceModel = recordField(latest, 'evidence_model');
|
|
150
|
+
const completionVerdict = recordField(latest, 'completion_verdict');
|
|
151
|
+
const completionEvidence = recordField(completionVerdict, 'evidence');
|
|
152
|
+
const verificationPlanId = stringField(latest, 'verification_plan_id') ?? stringField(evidenceModel, 'verification_plan_id');
|
|
153
|
+
const primaryReason = stringField(completionVerdict, 'primary_reason');
|
|
154
|
+
const status = stringField(latest, 'status') ?? stringField(completionVerdict, 'status') ?? 'unknown';
|
|
155
|
+
const completionStatus = stringField(completionVerdict, 'status');
|
|
156
|
+
const rawReceipts = recordArrayField(evidenceModel, 'receipts');
|
|
157
|
+
const rawCoverage = recordArrayField(evidenceModel, 'coverage_matrix');
|
|
158
|
+
const rawRequirements = recordArrayField(evidenceModel, 'requirements');
|
|
159
|
+
const rawRisks = recordArrayField(evidenceModel, 'remaining_risks');
|
|
160
|
+
const recordedFailureFingerprintRecord = recordField(latest, 'failure_fingerprint');
|
|
161
|
+
const repeatedFailureSummary = recordField(latest, 'repeated_failure_summary');
|
|
162
|
+
const reproEvidence = recordField(latest, 'repro_evidence') ?? recordField(evidenceModel, 'repro_evidence');
|
|
163
|
+
const reproductionRoute = recordField(reproEvidence, 'reproduction_route');
|
|
164
|
+
const recordedFailureFingerprint = stringField(recordedFailureFingerprintRecord, 'fingerprint');
|
|
165
|
+
const receipts = rawReceipts.length > 0
|
|
166
|
+
? rawReceipts.map((receipt, index) => ({
|
|
167
|
+
sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
|
|
168
|
+
ordinal: index + 1,
|
|
169
|
+
intent: stringField(receipt, 'intent'),
|
|
170
|
+
status: stringField(receipt, 'status') ?? 'unknown',
|
|
171
|
+
skipped: booleanField(receipt, 'skipped'),
|
|
172
|
+
verificationPlanId: stringField(receipt, 'verification_plan_id'),
|
|
173
|
+
receiptPath: stringField(receipt, 'receipt_path'),
|
|
174
|
+
receiptSha256: stringField(receipt, 'receipt_sha256'),
|
|
175
|
+
commandFingerprint: stringField(receipt, 'command_fingerprint'),
|
|
176
|
+
contractFingerprint: stringField(receipt, 'contract_fingerprint'),
|
|
177
|
+
currentStateHash: stringField(receipt, 'head_tree_hash') ??
|
|
178
|
+
stringField(receipt, 'changed_files_hash') ??
|
|
179
|
+
stringField(receipt, 'changed_file_hash'),
|
|
180
|
+
writeDriftStatus: stringField(receipt, 'write_drift_status'),
|
|
181
|
+
}))
|
|
182
|
+
: [
|
|
183
|
+
{
|
|
184
|
+
sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
|
|
185
|
+
ordinal: 1,
|
|
186
|
+
intent: stringField(latest, 'intent'),
|
|
187
|
+
status: evidenceStatusForRunReceipt(latest),
|
|
188
|
+
skipped: false,
|
|
189
|
+
verificationPlanId: null,
|
|
190
|
+
receiptPath: stringField(latest, 'receipt_path') ?? LATEST_RUN_STATE_RELATIVE_PATH,
|
|
191
|
+
receiptSha256: sourceHash,
|
|
192
|
+
commandFingerprint: stringField(recordField(latest, 'performance'), 'command_fingerprint'),
|
|
193
|
+
contractFingerprint: stringField(recordField(latest, 'performance'), 'contract_fingerprint'),
|
|
194
|
+
currentStateHash: stringField(latest, 'head_tree_hash') ?? stringField(latest, 'changed_files_hash'),
|
|
195
|
+
writeDriftStatus: stringField(recordField(latest, 'write_drift'), 'status'),
|
|
196
|
+
},
|
|
197
|
+
];
|
|
198
|
+
const coverageStates = rawCoverage.map((coverage) => {
|
|
199
|
+
const evidence = recordField(coverage, 'evidence');
|
|
200
|
+
return {
|
|
201
|
+
sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
|
|
202
|
+
criterionId: stringField(coverage, 'criterion_id') ?? 'unknown',
|
|
203
|
+
source: stringField(coverage, 'source') ?? 'unknown',
|
|
204
|
+
status: stringField(coverage, 'status') ?? 'unknown',
|
|
205
|
+
requirementReason: stringField(coverage, 'requirement_reason'),
|
|
206
|
+
intents: stringArrayField(evidence, 'intents'),
|
|
207
|
+
receiptCount: stringArrayField(evidence, 'receipt_paths').length,
|
|
208
|
+
gapCount: stringArrayField(evidence, 'gap_reasons').length,
|
|
209
|
+
sourceAnchorCount: stringArrayField(evidence, 'source_anchor_ids').length,
|
|
210
|
+
};
|
|
211
|
+
});
|
|
212
|
+
const riskSignals = rawRisks.map((risk, index) => ({
|
|
213
|
+
sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
|
|
214
|
+
ordinal: index + 1,
|
|
215
|
+
code: stringField(risk, 'code') ?? 'unknown',
|
|
216
|
+
severity: stringField(risk, 'severity') ?? 'unknown',
|
|
217
|
+
detailHash: sha256Text(stringField(risk, 'detail') ?? ''),
|
|
218
|
+
}));
|
|
219
|
+
const validationRatchetSignals = rawRisks
|
|
220
|
+
.map((risk, index) => {
|
|
221
|
+
const code = stringField(risk, 'code') ?? 'unknown';
|
|
222
|
+
if (!VALIDATION_RATCHET_RISK_CODES.has(code)) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
const severity = stringField(risk, 'severity') ?? 'unknown';
|
|
226
|
+
const pathValue = stringField(risk, 'path');
|
|
227
|
+
const detailHash = sha256Text(stringField(risk, 'detail') ?? '');
|
|
228
|
+
const pathHash = pathValue === null ? hashJson({ code, detailHash }) : sha256Text(pathValue);
|
|
229
|
+
const beforeHash = stringField(risk, 'before_hash') ?? stringField(risk, 'before_digest');
|
|
230
|
+
const afterHash = stringField(risk, 'after_hash') ?? stringField(risk, 'after_digest');
|
|
231
|
+
return {
|
|
232
|
+
signalId: hashJson({
|
|
233
|
+
sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
|
|
234
|
+
ordinal: index + 1,
|
|
235
|
+
planId: verificationPlanId,
|
|
236
|
+
code,
|
|
237
|
+
pathHash,
|
|
238
|
+
beforeHash,
|
|
239
|
+
afterHash,
|
|
240
|
+
}),
|
|
241
|
+
planId: verificationPlanId,
|
|
242
|
+
code,
|
|
243
|
+
severity,
|
|
244
|
+
pathHash,
|
|
245
|
+
beforeHash,
|
|
246
|
+
afterHash,
|
|
247
|
+
};
|
|
248
|
+
})
|
|
249
|
+
.filter((signal) => signal !== null);
|
|
250
|
+
const verificationPlans = verificationPlanId === null
|
|
251
|
+
? []
|
|
252
|
+
: [
|
|
253
|
+
{
|
|
254
|
+
planId: verificationPlanId,
|
|
255
|
+
sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
|
|
256
|
+
classificationHash: rawRequirements.length > 0 || rawCoverage.length > 0
|
|
257
|
+
? hashJson({
|
|
258
|
+
requirements: rawRequirements.map((requirement) => ({
|
|
259
|
+
id: stringField(requirement, 'requirement_id') ?? stringField(requirement, 'id'),
|
|
260
|
+
reason: stringField(requirement, 'reason'),
|
|
261
|
+
source: stringField(requirement, 'source'),
|
|
262
|
+
})),
|
|
263
|
+
coverage: rawCoverage.map((coverage) => ({
|
|
264
|
+
id: stringField(coverage, 'criterion_id'),
|
|
265
|
+
reason: stringField(coverage, 'requirement_reason'),
|
|
266
|
+
source: stringField(coverage, 'source'),
|
|
267
|
+
status: stringField(coverage, 'status'),
|
|
268
|
+
})),
|
|
269
|
+
})
|
|
270
|
+
: null,
|
|
271
|
+
commandContractHash: stringListHash(receipts.map((receipt) => receipt.contractFingerprint)),
|
|
272
|
+
selectedIntentsHash: stringListHash(receipts.map((receipt) => receipt.intent)),
|
|
273
|
+
createdAt: stringField(latest, 'started_at') ?? stringField(latest, 'created_at'),
|
|
274
|
+
sourceHash,
|
|
275
|
+
},
|
|
276
|
+
];
|
|
277
|
+
const acceptanceCriteria = verificationPlanId === null
|
|
278
|
+
? []
|
|
279
|
+
: rawCoverage.map((coverage) => {
|
|
280
|
+
const evidence = recordField(coverage, 'evidence');
|
|
281
|
+
const pathRefs = [
|
|
282
|
+
...stringArrayField(evidence, 'paths'),
|
|
283
|
+
...stringArrayField(evidence, 'changed_paths'),
|
|
284
|
+
...stringArrayField(evidence, 'source_anchor_ids'),
|
|
285
|
+
];
|
|
286
|
+
return {
|
|
287
|
+
criterionId: stringField(coverage, 'criterion_id') ?? 'unknown',
|
|
288
|
+
planId: verificationPlanId,
|
|
289
|
+
source: stringField(coverage, 'source') ?? 'unknown',
|
|
290
|
+
statementHash: stringField(coverage, 'statement') ? sha256Text(stringField(coverage, 'statement') ?? '') : null,
|
|
291
|
+
reason: stringField(coverage, 'requirement_reason'),
|
|
292
|
+
surface: stringField(coverage, 'surface'),
|
|
293
|
+
pathHash: pathRefs.length > 0 ? stringListHash(pathRefs) : null,
|
|
294
|
+
};
|
|
295
|
+
});
|
|
296
|
+
const criterionCoverage = verificationPlanId === null
|
|
297
|
+
? []
|
|
298
|
+
: coverageStates.map((coverage) => ({
|
|
299
|
+
criterionId: coverage.criterionId,
|
|
300
|
+
planId: verificationPlanId,
|
|
301
|
+
status: coverage.status,
|
|
302
|
+
receiptCount: coverage.receiptCount,
|
|
303
|
+
gapCount: coverage.gapCount,
|
|
304
|
+
riskCount: coverage.sourceAnchorCount,
|
|
305
|
+
}));
|
|
306
|
+
const commandReceiptSummaries = verificationPlanId === null
|
|
307
|
+
? []
|
|
308
|
+
: receipts
|
|
309
|
+
.filter((receipt) => receipt.verificationPlanId === verificationPlanId || receipt.verificationPlanId === null)
|
|
310
|
+
.map((receipt) => ({
|
|
311
|
+
receiptHash: receipt.receiptSha256 ??
|
|
312
|
+
hashJson({
|
|
313
|
+
sourcePath: receipt.sourcePath,
|
|
314
|
+
ordinal: receipt.ordinal,
|
|
315
|
+
intent: receipt.intent,
|
|
316
|
+
status: receipt.status,
|
|
317
|
+
verificationPlanId,
|
|
318
|
+
}),
|
|
319
|
+
planId: verificationPlanId,
|
|
320
|
+
intent: receipt.intent,
|
|
321
|
+
status: receipt.status,
|
|
322
|
+
commandFingerprint: receipt.commandFingerprint,
|
|
323
|
+
contractFingerprint: receipt.contractFingerprint,
|
|
324
|
+
currentStateHash: receipt.currentStateHash,
|
|
325
|
+
writeDriftStatus: receipt.writeDriftStatus,
|
|
326
|
+
}));
|
|
327
|
+
const completionVerdictSummaries = verificationPlanId === null || completionStatus === null
|
|
328
|
+
? []
|
|
329
|
+
: [
|
|
330
|
+
{
|
|
331
|
+
claimId: hashJson({
|
|
332
|
+
sourceHash,
|
|
333
|
+
verificationPlanId,
|
|
334
|
+
completionStatus,
|
|
335
|
+
primaryReason,
|
|
336
|
+
}),
|
|
337
|
+
planId: verificationPlanId,
|
|
338
|
+
status: completionStatus,
|
|
339
|
+
primaryReason,
|
|
340
|
+
riskCount: riskSignals.length,
|
|
341
|
+
contradictionCount: stringArrayField(completionVerdict, 'contradictions').length,
|
|
342
|
+
blockerCount: stringArrayField(completionVerdict, 'blockers').length,
|
|
343
|
+
},
|
|
344
|
+
];
|
|
345
|
+
const failedIntents = failedIntentsFromReceipts(receipts);
|
|
346
|
+
const failureFingerprint = recordedFailureFingerprint ??
|
|
347
|
+
createFailureFingerprint({
|
|
348
|
+
command,
|
|
349
|
+
status: completionStatus ?? status,
|
|
350
|
+
verificationPlanId,
|
|
351
|
+
primaryReason,
|
|
352
|
+
failedIntents,
|
|
353
|
+
riskCodes: riskSignals.map((risk) => risk.code),
|
|
354
|
+
runIntent: stringField(latest, 'intent'),
|
|
355
|
+
timedOut: booleanField(latest, 'timed_out'),
|
|
356
|
+
exitCodeClass: stringField(recordField(recordField(latest, 'performance'), 'result_summary'), 'exit_code_class'),
|
|
357
|
+
errorKind: stringField(recordField(recordField(latest, 'performance'), 'result_summary'), 'error_kind'),
|
|
358
|
+
});
|
|
359
|
+
const failureFingerprints = failureFingerprint === null
|
|
360
|
+
? []
|
|
361
|
+
: [
|
|
362
|
+
{
|
|
363
|
+
sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
|
|
364
|
+
fingerprint: failureFingerprint,
|
|
365
|
+
verificationPlanId,
|
|
366
|
+
status: completionStatus ?? status,
|
|
367
|
+
failedIntents,
|
|
368
|
+
primaryReason,
|
|
369
|
+
failedIntentsHash: stringField(recordedFailureFingerprintRecord, 'failed_intents_hash') ??
|
|
370
|
+
stringField(repeatedFailureSummary, 'failed_intents_hash'),
|
|
371
|
+
riskCodesHash: stringField(recordedFailureFingerprintRecord, 'risk_codes_hash') ??
|
|
372
|
+
stringField(repeatedFailureSummary, 'risk_codes_hash'),
|
|
373
|
+
affectedSurfacesHash: stringField(recordedFailureFingerprintRecord, 'affected_surfaces_hash') ??
|
|
374
|
+
stringField(repeatedFailureSummary, 'affected_surfaces_hash'),
|
|
375
|
+
firstSeenAt: stringField(repeatedFailureSummary, 'first_seen_at'),
|
|
376
|
+
lastSeenAt: stringField(repeatedFailureSummary, 'last_seen_at'),
|
|
377
|
+
seenCount: Math.max(1, numberField(repeatedFailureSummary, 'seen_count')),
|
|
378
|
+
requiresNewEvidence: booleanField(repeatedFailureSummary, 'requires_new_evidence'),
|
|
379
|
+
},
|
|
380
|
+
];
|
|
381
|
+
const routeId = stringField(reproductionRoute, 'route_id');
|
|
382
|
+
const reproRoutes = routeId === null || reproEvidence === null
|
|
383
|
+
? []
|
|
384
|
+
: [
|
|
385
|
+
{
|
|
386
|
+
routeId,
|
|
387
|
+
taskHash: hashJson({
|
|
388
|
+
reported_symptom: stringField(reproEvidence, 'reported_symptom'),
|
|
389
|
+
expected_behavior: stringField(reproEvidence, 'expected_behavior'),
|
|
390
|
+
observed_behavior: stringField(reproEvidence, 'observed_behavior'),
|
|
391
|
+
}),
|
|
392
|
+
routeDigest: stringField(reproductionRoute, 'route_digest'),
|
|
393
|
+
routeKind: stringField(reproductionRoute, 'route_kind'),
|
|
394
|
+
failureOracleHash: stringField(reproductionRoute, 'failure_oracle_hash'),
|
|
395
|
+
},
|
|
396
|
+
];
|
|
397
|
+
const reproObservations = routeId === null || reproEvidence === null
|
|
398
|
+
? []
|
|
399
|
+
: [
|
|
400
|
+
reproObservation(routeId, 'before_fix', recordField(reproEvidence, 'before_fix')),
|
|
401
|
+
reproObservation(routeId, 'after_fix', recordField(reproEvidence, 'after_fix')),
|
|
402
|
+
reproObservation(routeId, 'regression_guard', recordField(reproEvidence, 'regression_guard')),
|
|
403
|
+
];
|
|
404
|
+
const failureFingerprintReadModels = failureFingerprints.map((fingerprint) => ({
|
|
405
|
+
fingerprint: fingerprint.fingerprint,
|
|
406
|
+
planId: fingerprint.verificationPlanId,
|
|
407
|
+
failedIntentsHash: fingerprint.failedIntentsHash ?? stringListHash(fingerprint.failedIntents),
|
|
408
|
+
riskCodesHash: fingerprint.riskCodesHash,
|
|
409
|
+
seenCount: fingerprint.seenCount,
|
|
410
|
+
firstSeenAt: fingerprint.firstSeenAt,
|
|
411
|
+
lastSeenAt: fingerprint.lastSeenAt,
|
|
412
|
+
}));
|
|
413
|
+
return {
|
|
414
|
+
summaries: [
|
|
415
|
+
{
|
|
416
|
+
sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
|
|
417
|
+
sourceHash,
|
|
418
|
+
command,
|
|
419
|
+
kind,
|
|
420
|
+
status,
|
|
421
|
+
runDir: stringField(latest, 'run_dir'),
|
|
422
|
+
manifestPath: stringField(latest, 'manifest_path'),
|
|
423
|
+
verificationPlanId,
|
|
424
|
+
completionStatus,
|
|
425
|
+
primaryReason,
|
|
426
|
+
matchedIntents: numberField(completionEvidence, 'matched_intents'),
|
|
427
|
+
ranIntents: numberField(completionEvidence, 'ran_intents'),
|
|
428
|
+
passedIntents: numberField(completionEvidence, 'passed_intents'),
|
|
429
|
+
failedIntents: numberField(completionEvidence, 'failed_intents'),
|
|
430
|
+
skippedIntents: numberField(completionEvidence, 'skipped_intents'),
|
|
431
|
+
receiptCount: receipts.length,
|
|
432
|
+
coverageCount: coverageStates.length,
|
|
433
|
+
remainingRiskCount: riskSignals.length,
|
|
434
|
+
failureFingerprint,
|
|
435
|
+
},
|
|
436
|
+
],
|
|
437
|
+
verificationPlans,
|
|
438
|
+
acceptanceCriteria,
|
|
439
|
+
criterionCoverage,
|
|
440
|
+
receipts,
|
|
441
|
+
commandReceiptSummaries,
|
|
442
|
+
coverageStates,
|
|
443
|
+
riskSignals,
|
|
444
|
+
validationRatchetSignals,
|
|
445
|
+
completionVerdictSummaries,
|
|
446
|
+
failureFingerprints,
|
|
447
|
+
reproRoutes,
|
|
448
|
+
reproObservations,
|
|
449
|
+
failureFingerprintReadModels,
|
|
450
|
+
};
|
|
451
|
+
}
|