397777089e
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
62 lines
4.0 KiB
JavaScript
62 lines
4.0 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* criterion-green (Машина 5 Пакет 5, Блок 3, 5.6) — по-критерийный производитель GREEN.
|
||
*
|
||
* Для этой цели заменяет «весь-прогон-разом» enforce-verify-record: эмитит подписанный
|
||
* подписантом GREEN ТОЛЬКО при настоящем зелёном прогоне конкретного критерия. Два условия
|
||
* настоящего green (оба обязательны):
|
||
* - testPassed: тест изменённого шага реально прошёл;
|
||
* - mutationKilled (P18): сломали изменённый код — тест ОБЯЗАН покраснеть; выжил зелёным →
|
||
* тест ничего не проверяет → НЕ green.
|
||
* Иначе green:false с причиной (mutation-survived / test-not-passed / no-signer-key) — fail-CLOSE.
|
||
*
|
||
* Подпись — над аутентифицирующей тройкой {criterion_id, code_fingerprint, occurrence} (floor-signer,
|
||
* домен M5_GREEN). green/coverage_of_changed — поля записи; подлинность даёт подпись (Δ5),
|
||
* свежесть — отпечаток (Δ2). Чистый модуль: факты прогона (testPassed/mutationKilled/отпечаток/
|
||
* покрытие) и ключ подписанта инъектируются; никакого fs/исполнения тестов здесь (это — живая
|
||
* обёртка под активацией владельца). code_fingerprint считается отдельно через codeFingerprint.
|
||
*/
|
||
import { createHash } from 'node:crypto';
|
||
import { canonicalJson } from './receipt-sign.mjs';
|
||
import { signGreen } from './floor-signer.mjs';
|
||
|
||
/**
|
||
* Δ2: отпечаток «по делу» = sha256 канонизированной карты {путь: содержимое} изменённых
|
||
* файлов шага + тест-файлов, которые их прогоняли. Граф импортов НЕ считаем (кросс-язык —
|
||
* дорого/хрупко; надёжность даёт мутация P18). Правка любого из этих файлов после прогона →
|
||
* другой отпечаток → green аннулируется fingerprintFresh. Детерминирован (canonicalJson сортит
|
||
* ключи — порядок файлов не влияет).
|
||
*/
|
||
export function codeFingerprint(fileContents = {}) {
|
||
return createHash('sha256').update(canonicalJson(fileContents)).digest('hex');
|
||
}
|
||
|
||
/**
|
||
* Произвести по-критерийный GREEN. Настоящий green ⇔ testPassed && mutationKilled && есть ключ.
|
||
* @returns настоящий green: {criterion_id, code_fingerprint, occurrence, sig, green:true, coverage_of_changed}
|
||
* иначе: {criterion_id, green:false, code_fingerprint, coverage_of_changed, occurrence, reason}
|
||
*/
|
||
export function produceGreen({
|
||
criterion_id,
|
||
occurrence,
|
||
code_fingerprint,
|
||
coverage_of_changed = null,
|
||
testPassed = false,
|
||
mutationKilled = false,
|
||
signerKey,
|
||
} = {}) {
|
||
if (testPassed !== true) {
|
||
return { criterion_id, green: false, code_fingerprint, coverage_of_changed, occurrence, reason: 'test-not-passed' };
|
||
}
|
||
if (mutationKilled !== true) {
|
||
// P18: тест прошёл, но мутация выжила — тест не проверяет изменённый код → не green.
|
||
return { criterion_id, green: false, code_fingerprint, coverage_of_changed, occurrence, reason: 'mutation-survived' };
|
||
}
|
||
const receipt = signGreen({ criterion_id, code_fingerprint, occurrence }, signerKey);
|
||
if (!receipt) {
|
||
// Нет ключа подписанта → подлинный green произвести нельзя (fail-CLOSE).
|
||
return { criterion_id, green: false, code_fingerprint, coverage_of_changed, occurrence, reason: 'no-signer-key' };
|
||
}
|
||
return { ...receipt, green: true, coverage_of_changed };
|
||
}
|