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:
parent
2598c4953a
commit
9bd17c401d
21 changed files with 218 additions and 13 deletions
|
|
@ -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' };
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue