Files
brain/tools/owner-consent.test.mjs
T
Дмитрий dec0ed502a feat: терминальный грант владельца — примитив (consent forgery B1)
Несомненный канал согласия для тяжёлого (Поза 1): floor_escape с origin:owner-terminal,
подписанный ключом keychain. Скрипт владельца tools/owner-consent.mjs строит+подписывает+
пишет грант (запускает владелец; контроллер не может — нет ключа + floor режет запись runtime).
Читатель loadTerminalGrants принимает ТОЛЬКО origin-помеченные валидно-подписанные гранты,
ключ обязателен (fail-closed, #KEY). Нулевой радиус: живые потребители НЕ тронуты —
подключение (owner-seal/ops-runbook/commit/plan-done/gate3/bash) отдельными заходами B2+.
Спека: specs/2026-06-18-consent-forgery-fix-design.md §B/§KEY.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 18:02:28 +03:00

42 lines
2.1 KiB
JavaScript

import { describe, it, expect } from 'vitest';
import { buildTerminalGrant, writeTerminalGrant } from './owner-consent.mjs';
import { OWNER_TERMINAL_ORIGIN, loadTerminalGrants } from './escape-grant.mjs';
import { verifyFloorEscapeRecord } from './askuser-answer-parser.mjs';
const KEY = 'oc-key';
// in-memory fs (как escape-grant.test): пишем в Map, читаем обратно
function memFs() {
const s = new Map(); const norm = (p) => String(p).replace(/\\/g, '/');
return { s, norm,
appendFileSync: (p, d) => { const n = norm(p); s.set(n, (s.get(n) || '') + d); },
mkdirSync: () => {},
existsSync: (p) => s.has(norm(p)),
readFileSync: (p) => s.get(norm(p)) || '' };
}
describe('owner-consent — терминальный грант владельца (Часть B)', () => {
it('buildTerminalGrant ставит origin и тип', () => {
expect(buildTerminalGrant('owner-seal:abc', 7)).toEqual({
type: 'floor_escape', action: 'owner-seal:abc', origin: OWNER_TERMINAL_ORIGIN, ts: 7 });
});
it('writeTerminalGrant пишет подписанный грант, который читает loadTerminalGrants', () => {
const fs = memFs();
const r = writeTerminalGrant({ sessionId: 's1', action: 'owner-seal:abc', nowMs: 100, key: KEY, runtimeDir: '/rt', fsImpl: fs });
expect(r.signed).toBe(true);
const raw = JSON.parse((fs.s.get('/rt/askuser-decisions-s1.jsonl') || '').trim());
expect(raw.origin).toBe(OWNER_TERMINAL_ORIGIN);
expect(verifyFloorEscapeRecord(raw, KEY)).toBe(true);
expect(loadTerminalGrants('s1', 100, { keyImpl: () => KEY, fsImpl: fs, runtimeDir: '/rt' }))
.toEqual([{ action: 'owner-seal:abc', ts: 100 }]);
});
it('без ключа грант не подписан → loadTerminalGrants его отвергает', () => {
const fs = memFs();
const r = writeTerminalGrant({ sessionId: 's2', action: 'commit:xyz', nowMs: 100, key: null, runtimeDir: '/rt', fsImpl: fs });
expect(r.signed).toBe(false);
expect(loadTerminalGrants('s2', 100, { keyImpl: () => KEY, fsImpl: fs, runtimeDir: '/rt' })).toEqual([]);
});
});