feat(permission): user-facing permission presets — one Permissions select over the two knobs
A preset names a bundle of the two mechanism knobs — request = workspace-write + ask, yolo = danger-full-access + never — so the editor shows ONE 'Permissions' select where the sandbox-mode and approval-policy tiers stay orthogonal capabilities (the Codex /approvals shape: presets over two dials). ctx.permission (dsh-permission) owns the config-defined table, validates the default preset's bundle against the composed knob defaults at load (fails loud), and writes a switch THROUGH: one log-only permission/preset event (the audit fact reverse-mapping cannot recover — the planned 'agent' preset shares request's knob values and differs only in composed policy) plus each knob event via its own setter, deduped — a net-zero switch appends nothing. Every knob consumer keeps reading its own fold, untouched. The current preset DERIVES from the effective knob values — the fold breaks bundle ties, a knob state outside the table is the reserved 'custom' value (a state, not an error: shown while it holds, switchable FROM, never a target), and defaultPreset disappears (zero-event state reverse-maps from the composition defaults). The ACP bridge drops the two per-knob selects for the one preset select (advertised only when ctx.permission is composed); pending/anchor/no-op semantics carry over unchanged, with the no-op echo acknowledged before vocabulary validation so a client re-pushing a derived 'custom' current never errors. The sandbox variant example composes the service with a workspace-write default; the permission-switching, escalation-approved and escalation-rejected scenarios are re-recorded under it (escalations now target an outside-workspace /tmp path under danger-full-access, self-cleaning) and config-options is re-authored on the single-select wire.
This commit is contained in:
@@ -140,20 +140,24 @@ const SCENARIOS: Scenario[] = [
|
||||
// prior denial. config-options: the session config-option surface this
|
||||
// composition adds (both advertised selects, the refreshed state every
|
||||
// set_config_option answers with, both rejection shapes) — protocol-only,
|
||||
// replays on runner-less hosts. mode-switching: the runtime switching arc
|
||||
// and NECESSARILY this class's pinned-header scenario (an approval-policy
|
||||
// switch rewrites its prompt section; the resulting request/header-delta is
|
||||
// legal only in the pinning scenario) — the pin commits the full sandbox
|
||||
// header (persona, tool schemas WITH the escalation fields) plus the
|
||||
// approval delta and its "changed by the user" notice; the SANDBOX switch
|
||||
// stays deliberately silent (the visibility asymmetry), proven by BEHAVIOR.
|
||||
// replays on runner-less hosts. permission-switching: the runtime
|
||||
// preset-switch arc (request → yolo: one permission/preset event written
|
||||
// through to both knobs) and NECESSARILY this class's pinned-header
|
||||
// scenario (yolo's approval=never rewrites the prompt section; the
|
||||
// resulting request/header-delta is legal only in the pinning scenario) —
|
||||
// the pin commits the full sandbox header (persona, tool schemas WITH the
|
||||
// escalation fields) plus the approval delta and its "changed by the user"
|
||||
// notice; the sandbox half of the bundle stays deliberately silent (the
|
||||
// visibility asymmetry) and is pinned as its stamped knob event.
|
||||
// escalation-approved/rejected: the approval wire end-to-end under the
|
||||
// default read-only/ask — the escalating call streams,
|
||||
// session/request_permission attaches to it, and the scripted answer drives
|
||||
// each branch (approved runs CONFINED under the granted workspace-write;
|
||||
// rejected executes nothing, failing with the deterministic text).
|
||||
// default request preset (workspace-write/ask) — the escalating call
|
||||
// (danger-full-access for a user-asserted outside-workspace denial)
|
||||
// streams, session/request_permission attaches to it, and the scripted
|
||||
// answer drives each branch (approved runs under the granted mode and
|
||||
// self-cleans its /tmp target; rejected executes nothing, failing with
|
||||
// the deterministic text).
|
||||
{ name: 'config-options', hasModelTurn: false, recorded: false, headerClass: 'sandbox', configPath: SANDBOX_CONFIG },
|
||||
{ name: 'mode-switching', hasModelTurn: true, recorded: true, pinsHeader: true, expectedHeaderDeltas: 1, headerClass: 'sandbox', configPath: SANDBOX_CONFIG },
|
||||
{ name: 'permission-switching', hasModelTurn: true, recorded: true, pinsHeader: true, expectedHeaderDeltas: 1, headerClass: 'sandbox', configPath: SANDBOX_CONFIG },
|
||||
{ name: 'escalation-approved', hasModelTurn: true, recorded: true, headerClass: 'sandbox', configPath: SANDBOX_CONFIG },
|
||||
{ name: 'escalation-rejected', hasModelTurn: true, recorded: true, headerClass: 'sandbox', configPath: SANDBOX_CONFIG },
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user