Bypass mode (owner opt-in, default off): storebound settings bypass on|off|status and a one-time question in init; MCP owner_step (owner-only commands, dry run first), owner-only api operations with --yes --confirm, console plans may press buttons.final; every action logged to .storebound/owner-actions.log; docs/bypass.md, agent rules, tests

This commit is contained in:
CRTkafa 2026-10-22 02:27:34 +08:00
parent 2598c4953a
commit 9bd17c401d
21 changed files with 218 additions and 13 deletions

View file

@ -83,6 +83,8 @@ export interface GateInput {
confirm?: string;
/** the operation id the call resolved to (what --confirm must equal) */
opId?: string;
/** the owner turned bypass mode on (storebound settings bypass on): owner-only operations take --confirm too */
bypass?: boolean;
}
export type Decision =
@ -102,16 +104,17 @@ export const OWNER_NO_CONFIRM = 'refused: owner-only operation: --confirm does n
export function decide(g: GateInput): Decision {
if (g.getOnly && g.method.toUpperCase() !== 'GET') return { action: 'refuse', why: 'api get runs GET operations only' };
if (g.kind === 'read') return g.dry ? { action: 'dry', why: '--dry' } : { action: 'send' };
if (g.agent) return { action: 'refuse', why: g.kind === 'owner'
if (g.agent && !g.bypass) return { action: 'refuse', why: g.kind === 'owner'
? 'owner-only operation: never through MCP or the desktop app; the owner runs it in a terminal'
: 'writes through `storebound api` are not available to MCP or the desktop app; run it in a terminal' };
if (g.dry || !g.yes) return { action: 'dry', why: g.dry ? '--dry' : 'writes are dry by default: add --yes (in a terminal) to send' };
if (g.confirm !== undefined) {
// an agent's typed confirmation: only for plain writes, only when it names this exact operation
if (g.kind === 'owner') return { action: 'refuse', why: OWNER_NO_CONFIRM };
if (g.kind === 'owner' && !g.bypass) return { action: 'refuse', why: OWNER_NO_CONFIRM };
if (!g.opId || g.confirm !== g.opId) return { action: 'refuse', why: `refused: --confirm ${g.confirm} does not equal the operation id ${g.opId ?? '?'}. Nothing was sent.` };
return { action: 'send' };
}
if (g.agent) return { action: 'refuse', why: 'bypass mode: an agent sends writes with --yes --confirm <operationId>' };
if (!g.tty) return { action: 'refuse', why: NOT_A_TTY };
return g.kind === 'owner' ? { action: 'confirm', typed: true } : { action: 'send' };
}