feat(wall): research-read MCP в разговоре (egress-страж нетронут, тяжёлый firecrawl исключён)
Правка машинерии №4 скила surfacing-open-questions (спека v6, раздел stena4): isResearchRead пускает поимённо perplexity/exa/firecrawl read-инструменты в разговорной фазе; crawl/agent/interact/monitor/map исключены. egress-страж не тронут. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -47,6 +47,20 @@ export function isLedgerAppend(toolUse) {
|
||||
return LEDGER_PATH_RE.test(fp);
|
||||
}
|
||||
|
||||
// Окно research-read (№4): поимённо read-инструменты веб-разведки в разговорной фазе. Пин по суффиксу;
|
||||
// тяжёлый/мутирующий firecrawl (crawl/agent/interact/monitor/map/feedback) исключён. egress-страж
|
||||
// (enforce-mcp-classification) отдельно сканит payload — здесь снимаем только фазовый блок.
|
||||
const RESEARCH_READ_SUFFIXES = [
|
||||
'perplexity_search', 'perplexity_ask', 'perplexity_research',
|
||||
'web_search_exa', 'web_fetch_exa',
|
||||
'firecrawl_search', 'firecrawl_scrape', 'firecrawl_extract', 'firecrawl_parse',
|
||||
];
|
||||
export function isResearchRead(toolUse) {
|
||||
const n = String(toolUse?.name || '');
|
||||
if (!n.startsWith('mcp__')) return false;
|
||||
return RESEARCH_READ_SUFFIXES.some((s) => n.endsWith('__' + s) || n.endsWith(s));
|
||||
}
|
||||
|
||||
// Узкий технический allowlist загрузки (НЕ «карта критического») — без него
|
||||
// нельзя создать первый план: writing-plans пишет план, AskUser/EnterPlanMode
|
||||
// открывают одобрение. Обоснование — D12/D13.
|
||||
@@ -419,14 +433,14 @@ export function decideMode({ toolUse, frozenPlan, frozenArtifact, stepPtr = 0, k
|
||||
return { decision: 'allow', mode: 'conversational', finishPlan: true, reason: 'владелец завершил план досрочно (plan-done) — печать снята, возврат в разговор' };
|
||||
}
|
||||
if (!frozenPlan) {
|
||||
if (isSeed(toolUse) || isObserveOnly(toolUse) || isQueryOnly(toolUse) || isAuthoringWrite(toolUse) || isLedgerAppend(toolUse)) return { decision: 'allow', mode: 'conversational', reason: 'seed/observe/query/authoring (разговорный режим)' };
|
||||
if (isSeed(toolUse) || isObserveOnly(toolUse) || isQueryOnly(toolUse) || isAuthoringWrite(toolUse) || isLedgerAppend(toolUse) || isResearchRead(toolUse)) return { decision: 'allow', mode: 'conversational', reason: 'seed/observe/query/authoring (разговорный режим)' };
|
||||
return { decision: 'block', mode: 'conversational', reason: 'разговорный режим: только думать/спрашивать (реализация — после печати артефакта и плана)' };
|
||||
}
|
||||
if (!frozenArtifact || !verifyArtifactImpl(frozenArtifact, key)) {
|
||||
// F-B (аудит 2026-06-07): observe-only (Read/Grep/Glob/readonly-Bash/TodoWrite) пускаем
|
||||
// и в этом деградированном состоянии — инвариант finding 9 «смотрящие не душатся» +
|
||||
// согласованность с decide() (там observe-only allow безусловно). Бэкстоп держит только мутаторы.
|
||||
if (isSeed(toolUse) || isObserveOnly(toolUse) || isQueryOnly(toolUse) || isAuthoringWrite(toolUse) || isLedgerAppend(toolUse)) return { decision: 'allow', mode: 'conversational', reason: 'seed/observe/query/authoring (бэкстоп: артефакт не опечатан)' };
|
||||
if (isSeed(toolUse) || isObserveOnly(toolUse) || isQueryOnly(toolUse) || isAuthoringWrite(toolUse) || isLedgerAppend(toolUse) || isResearchRead(toolUse)) return { decision: 'allow', mode: 'conversational', reason: 'seed/observe/query/authoring (бэкстоп: артефакт не опечатан)' };
|
||||
return { decision: 'block', mode: 'conversational', reason: 'нет опечатанного артефакта разговорной фазы — вернись в разговор (бэкстоп C-10)' };
|
||||
}
|
||||
// SE-2 (fail-closed whitelist): энфорсмент ТОЛЬКО при live-block на ОБЕИХ печатях.
|
||||
|
||||
@@ -10,6 +10,24 @@ import { resolveSessionId } from './enforce-supreme-gate.mjs';
|
||||
import { signStepState, verifyStepState } from './enforce-supreme-gate.mjs';
|
||||
import { stepStatePath } from './enforce-supreme-gate.mjs';
|
||||
import { isLedgerAppend } from './enforce-supreme-gate.mjs';
|
||||
import { isResearchRead } from './enforce-supreme-gate.mjs';
|
||||
|
||||
describe('isResearchRead (окно research-read №4)', () => {
|
||||
const t = (name) => isResearchRead({ name });
|
||||
it('пускает поимённо read-инструменты research', () => {
|
||||
for (const n of ['mcp__perplexity__perplexity_search', 'mcp__perplexity__perplexity_ask',
|
||||
'mcp__perplexity__perplexity_research', 'mcp__exa__web_search_exa', 'mcp__exa__web_fetch_exa',
|
||||
'mcp__firecrawl__firecrawl_search', 'mcp__firecrawl__firecrawl_scrape',
|
||||
'mcp__firecrawl__firecrawl_extract', 'mcp__firecrawl__firecrawl_parse'])
|
||||
expect(t(n)).toBe(true);
|
||||
});
|
||||
it('НЕ пускает тяжёлый/мутирующий firecrawl и не-mcp', () => {
|
||||
for (const n of ['mcp__firecrawl__firecrawl_crawl', 'mcp__firecrawl__firecrawl_agent',
|
||||
'mcp__firecrawl__firecrawl_interact', 'mcp__firecrawl__firecrawl_monitor_create',
|
||||
'mcp__firecrawl__firecrawl_map', 'mcp__firecrawl__firecrawl_search_feedback', 'WebSearch'])
|
||||
expect(t(n)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isLedgerAppend (окно журнала №3)', () => {
|
||||
const ok = (fp, name = 'Write') => isLedgerAppend({ name, input: { file_path: fp } });
|
||||
|
||||
Reference in New Issue
Block a user