feat(m5): fingerprintFresh — свежесть отпечатка green (Пакет 5, 5.3, Δ2)
Δ2: зелёный прогон засчитывается только если его code_fingerprint совпадает с текущим (изменённые файлы шага + тесты; currentFingerprints инъектируется живым гейтом). Правка файла после прогона → расхождение → green аннулирован (stale). Чистая функция, fail-CLOSE (нет текущего отпечатка ≠ записанному → stale). Красные прогоны не проверяются (их «не-зелёность» ловит criteriaGreenMatched). По авторитетному Δ6 — ОТДЕЛЬНЫЙ шаг лесенки критерий-гейта (не внутрь criteriaGreenMatched, которая остаётся «green-присутствием», Δ6 шаг 2). +5 тестов. judge-gate-floor 32/32 (чисто аддитивно). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -69,13 +69,30 @@ export function k5CriterionCheck({ planSteps = [] }) {
|
||||
return { ok: missingCriterion.length === 0, missingCriterion };
|
||||
}
|
||||
|
||||
/** Гейт-3 пол: каждый объявленный критерий сопоставлен с настоящим ЗЕЛЁНЫМ прогоном. */
|
||||
/** Гейт-3 пол: каждый объявленный критерий сопоставлен с настоящим ЗЕЛЁНЫМ прогоном.
|
||||
* Это «green-присутствие» (Δ6 шаг 2): свежесть отпечатка и подпись — отдельные шаги. */
|
||||
export function criteriaGreenMatched({ criteria = [], greenRuns = [] }) {
|
||||
const greenIds = new Set((greenRuns || []).filter((r) => r && r.green === true).map((r) => r.criterion_id));
|
||||
const unproven = (criteria || []).filter((c) => !greenIds.has(c.id)).map((c) => c.id);
|
||||
return { ok: unproven.length === 0, unproven };
|
||||
}
|
||||
|
||||
/**
|
||||
* 5.3 (Δ2, Δ6 шаг 3): свежесть отпечатка. Зелёный прогон засчитывается только если его
|
||||
* code_fingerprint совпадает с ТЕКУЩИМ (изменённые файлы шага + тест-файлы; считается живым
|
||||
* гейтом, инъектируется как currentFingerprints[criterion_id]). Правка файла после прогона
|
||||
* меняет текущий отпечаток → расхождение → green аннулирован (stale). Чистая функция.
|
||||
* Конструктивно fail-CLOSE: нет текущего отпечатка (undefined) ≠ записанному → stale.
|
||||
* Красные прогоны не проверяются (их «не-зелёность» ловит criteriaGreenMatched).
|
||||
*/
|
||||
export function fingerprintFresh({ greenRuns = [], currentFingerprints = {} }) {
|
||||
const stale = (greenRuns || [])
|
||||
.filter((r) => r && r.green === true)
|
||||
.filter((r) => r.code_fingerprint !== currentFingerprints[r.criterion_id])
|
||||
.map((r) => r.criterion_id);
|
||||
return { ok: stale.length === 0, stale };
|
||||
}
|
||||
|
||||
/** A2 K2: требуемые навыки взяты по фактическому журналу вызовов (не по тексту). */
|
||||
export function skillTakenByJournal({ requiredSkills = [], journalSkillCalls = [] }) {
|
||||
const missing = skillsCoveredByJournal(requiredSkills, journalSkillCalls);
|
||||
|
||||
@@ -164,3 +164,46 @@ describe('criteriaFromSealedPlan (#5, F3/F9 — критерии из печат
|
||||
expect(r.unsealed).toEqual(['c9']);
|
||||
});
|
||||
});
|
||||
|
||||
// 5.3 (Δ2): свежесть отпечатка в judge-gate-floor — green засчитывается только при совпадении
|
||||
// code_fingerprint зелёного прогона с ТЕКУЩИМ (изменённые файлы шага + тесты). Правка файла
|
||||
// после прогона меняет текущий отпечаток → green аннулируется. Отдельный шаг лесенки (Δ6).
|
||||
import { fingerprintFresh } from './judge-gate-floor.mjs';
|
||||
|
||||
describe('fingerprintFresh (5.3, Δ2): green свеж только при совпадении code_fingerprint', () => {
|
||||
it('отпечаток зелёного совпадает с текущим → свеж (ok)', () => {
|
||||
const r = fingerprintFresh({
|
||||
greenRuns: [{ criterion_id: 'c1', green: true, code_fingerprint: 'aa' }],
|
||||
currentFingerprints: { c1: 'aa' },
|
||||
});
|
||||
expect(r.ok).toBe(true);
|
||||
expect(r.stale).toEqual([]);
|
||||
});
|
||||
it('файл изменён после прогона (отпечаток разошёлся) → stale, не ok', () => {
|
||||
const r = fingerprintFresh({
|
||||
greenRuns: [{ criterion_id: 'c1', green: true, code_fingerprint: 'aa' }],
|
||||
currentFingerprints: { c1: 'bb' },
|
||||
});
|
||||
expect(r.ok).toBe(false);
|
||||
expect(r.stale).toEqual(['c1']);
|
||||
});
|
||||
it('нет текущего отпечатка для критерия → stale (fail-CLOSE)', () => {
|
||||
const r = fingerprintFresh({
|
||||
greenRuns: [{ criterion_id: 'c1', green: true, code_fingerprint: 'aa' }],
|
||||
currentFingerprints: {},
|
||||
});
|
||||
expect(r.ok).toBe(false);
|
||||
expect(r.stale).toEqual(['c1']);
|
||||
});
|
||||
it('красный прогон (green:false) не проверяется на свежесть (его забота — criteriaGreenMatched)', () => {
|
||||
const r = fingerprintFresh({
|
||||
greenRuns: [{ criterion_id: 'c1', green: false, code_fingerprint: 'aa' }],
|
||||
currentFingerprints: { c1: 'bb' },
|
||||
});
|
||||
expect(r.ok).toBe(true);
|
||||
expect(r.stale).toEqual([]);
|
||||
});
|
||||
it('нет зелёных прогонов → ok (присутствие зелёного — отдельный шаг)', () => {
|
||||
expect(fingerprintFresh({ greenRuns: [], currentFingerprints: {} }).ok).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user