feat: round-memory ownerSealActionForContent per-stage hash helper SP3-c2a

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-06-17 03:42:22 +03:00
parent ce58f37d8f
commit f96610cd7b
2 changed files with 25 additions and 2 deletions
+14 -1
View File
@@ -14,7 +14,7 @@ import { buildArtifact } from './artifact-from-spec.mjs';
import { parsePlanSteps } from './plan-steps-parse.mjs';
import { parsePlanSkills } from './plan-skills.mjs';
import { contentHash, sealOnApproval, requiresOwnerSeal } from './judge-seal-channel.mjs';
import { freezeArtifact, freezePlan } from './plan-lock.mjs';
import { freezeArtifact, freezePlan, planId } from './plan-lock.mjs';
export function sealableArtifact(md) { return buildArtifact(md); } // {sections, source_sha}
export function sealablePlan(md) { return { steps: parsePlanSteps(md), skills: parsePlanSkills(md) }; } // {steps,skills}
@@ -90,3 +90,16 @@ export function decideSeal({ verdict, ownerSealOpen = false, ownerSealRequired =
if (isRealGo(verdict) && ownerSealRequired) return { seal: false, via: null, ownerRequired: true };
return { seal: false, via: null };
}
/** SP3-c: owner-seal-метка для карточки арбитража по содержимому. ТОТ ЖЕ хеш, что считает
* sealTurnProd → подпись владельца на этой метке сматчится: план (есть steps) → planId(steps);
* иначе спека → judgedHashOf(sealableArtifact). Тотально — null при сбое (карточка тогда без
* точной escape-строки, владелец берёт хеш из логов). */
export function ownerSealActionForContent(content) {
const c = String(content == null ? '' : content);
try {
const steps = sealablePlan(c).steps;
if (Array.isArray(steps) && steps.length) return ownerSealAction(planId(steps));
} catch { /* не план — пробуем как спеку */ }
try { return ownerSealAction(judgedHashOf(sealableArtifact(c))); } catch { return null; }
}
+11 -1
View File
@@ -1,6 +1,7 @@
import { describe, it, expect } from 'vitest';
import { sealableArtifact, sealablePlan, judgedHashOf, sealArtifact, sealPlan, ownerSealAction, decideSeal } from './seal-orchestration.mjs';
import { sealableArtifact, sealablePlan, judgedHashOf, sealArtifact, sealPlan, ownerSealAction, decideSeal, ownerSealActionForContent } from './seal-orchestration.mjs';
import { contentHash } from './judge-seal-channel.mjs';
import { planId } from './plan-lock.mjs';
const specMd = '## Реш {#dec-a}\nтекст';
const planMd = '```steps-json\n[{"op":"Edit","object":"app/Foo.php","ref":"dec-a"}]\n```';
@@ -102,3 +103,12 @@ describe('owner-seal в seal-функциях (SP3-b3)', () => {
expect(r.ownerRequired).toBe(true);
});
});
describe('ownerSealActionForContent (SP3-c)', () => {
it('план (есть steps-json) → owner-seal:planId(steps) (как в sealTurnProd)', () => {
expect(ownerSealActionForContent(planMd)).toBe(ownerSealAction(planId(sealablePlan(planMd).steps)));
});
it('спека (нет steps) → owner-seal:judgedHashOf(sealableArtifact) (как в sealTurnProd)', () => {
expect(ownerSealActionForContent(specMd)).toBe(ownerSealAction(judgedHashOf(sealableArtifact(specMd))));
});
});