// Bypass mode: off by default; when the owner turns it on, owner-only steps open to the agent and every one is logged. import { describe, expect, test } from 'bun:test'; import { mkdtempSync, readFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { decide } from '../cli/api/gate'; import { bypassOn, logOwnerAction, setBypass } from '../cli/lib/bypass'; import { listTools, OWNER_STEP, toArgv } from '../cli/mcp-tools'; const owner = { kind: 'owner' as const, method: 'POST', dry: false, yes: true, tty: false, agent: true, getOnly: false, confirm: 'reviewSubmissions_createInstance', opId: 'reviewSubmissions_createInstance' }; describe('bypass mode', () => { test('off by default; on / off per machine (settings.json)', () => { const env = { STOREBOUND_HOME: mkdtempSync(join(tmpdir(), 'sb-home-')) }; expect(bypassOn(env)).toBe(false); setBypass(true, env); expect(bypassOn(env)).toBe(true); setBypass(false, env); expect(bypassOn(env)).toBe(false); }); test('the api gate: owner operations refused for an agent unless bypass, then only with --yes --confirm ', () => { expect(decide(owner).action).toBe('refuse'); expect(decide({ ...owner, bypass: true }).action).toBe('send'); expect(decide({ ...owner, bypass: true, confirm: 'other' }).action).toBe('refuse'); expect(decide({ ...owner, bypass: true, confirm: undefined }).action).toBe('refuse'); expect(decide({ ...owner, bypass: true, yes: false }).action).toBe('dry'); }); test('MCP lists owner_step only in bypass mode; dry run first', () => { expect(listTools(false).some(t => t.name === 'owner_step')).toBe(false); expect(listTools(true).some(t => t.name === 'owner_step')).toBe(true); expect(toArgv(OWNER_STEP, { command: 'apple submit', args: ['--build', '42'] }).argv).toEqual(['apple', 'submit', '--build', '42', '--dry']); expect(toArgv(OWNER_STEP, { command: 'play rollout', args: ['--percent', '20'], dry_run: false })).toEqual({ argv: ['play', 'rollout', '--percent', '20'], dryRun: false }); }); test('every owner action is logged in the project', () => { const root = mkdtempSync(join(tmpdir(), 'sb-owner-')); const f = logOwnerAction(root, { command: 'storebound apple submit --build 42 --yes', dry: false, exit: 0, summary: 'submitted' }); expect(readFileSync(f, 'utf8')).toMatch(/\tRAN\texit 0\tstorebound apple submit --build 42 --yes\tsubmitted\n$/); }); });