approval-gate
v1.0.0-rc.1Policy and decision surface for human-held function calls — pre_trigger gate, pending inbox, per-session permission settings, and two notification trigger types.
- macOS: arm64 · x64
- Linux: arm64 · armv7 · x64
- Windows: arm64 · x64 · x86
exact versions are immutable; binary and bundle artifacts are digest-pinned.
full markdown
/workers/approval-gate.md?version=1.0.0-rc.1. paste it into an llm prompt or pipe it through curl from a worker.install
dependencies
readme
approval-gate
The policy and decision surface for human-held function calls (spec). Three surfaces, one worker:
- The gate —
approval::gate, apre_triggerhook the worker binds itself at startup on the harness'sharness::hook::pre-triggertrigger type. It evaluates per-session mode, allow-lists, and inline configrules, and answerscontinue,deny, orhold. - The decision plane —
approval::resolveplus the per-session settings RPCs (set_mode,add_always_allow,approve_always, …). Human/console only. - The pending inbox — an ephemeral index of held calls
(
approval::list-pending/approval::get-pending) plus two trigger types (approval::pending-created/approval::pending-resolved) that notification workers and UIs bind to.
The worker keeps no resolved-approval history: a record exists only while
a call is held; every record has an explicit deletion path (resolve, turn
abort, session delete). The transcript's function_result and the
pending_resolved event are the audit trail. Holds do not expire — they wait
until a human resolves or the turn/session is purged.
Standalone caveat
This worker codes against the greenfield harness contracts
(harness::hook::pre-trigger, harness::function::resolve,
harness::turn-completed — see harness.md § Hooks / § API Reference), which
are not implemented by the current harness yet. The harness/session
trigger bindings are best-effort: on an engine without those trigger types the
worker still boots, serves its RPCs, and logs trigger_type_not_found for the
absent bindings (restart it after the sibling appears to re-bind). The
configuration worker is the one hard dependency — it is enabled by default in
the engine, and a failed register/fetch aborts boot. The integration suite
exercises the harness surface against in-process fakes until harness 1.0 lands.
Install
iii worker add approval-gateQuickstart
cargo build
./target/debug/approval-gate --url ws://127.0.0.1:49134The authoritative config lives in the engine's configuration worker (no
committed config.yaml); pass --config only to seed the entry on
its very first registration.
Hold → decide → release, from any client:
# A held call shows up in the inbox…
iii call approval::list-pending '{}'
# …a human allows it (the harness re-runs it through trigger)…
iii call approval::resolve '{"session_id": "s_1", "function_call_id": "c_1", "decision": "allow"}'
# …or denies it with a reason the model can adapt to.
iii call approval::resolve '{"session_id": "s_1", "function_call_id": "c_1", "decision": "deny", "reason": "not on prod"}'Permission model
Per-session mode plus two allow-lists, evaluated in this order (ported unchanged from the proven implementation):
approval::*/configuration::*target → deny (human_only_function, even underfull— self-escalation defense)- mode
full→ allow approved_alwayshit → allow (every mode — remembered human decisions)- mode
autoandalways_allowhit → allow (dormant undermanual) - fall through to configuration
rules(first match wins):allow→ allow ·deny→ deny · no match → hold
When the configuration entry omits rules, the gate denies only this
worker's own approval::* surface; every other call holds. On startup the
worker seeds/backfills the stored entry so rules are editable in the console.
Custom trigger types
| Type | Fires | Payload |
|---|---|---|
approval::pending-created |
a call was held and its inbox record written (async, off the hot path) | PendingApprovalRecord & { status: "pending" } — redacted args, session context: self-sufficient for notification copy |
approval::pending-resolved |
a pending call left the inbox (exactly once per record) | ids + outcome: "allow" | "deny" | "aborted", operator reason on deny |
Binding config (both types): { session_id?, metadata? } — metadata is a
subset-equality match against the record's denormalized session_metadata,
so a multi-tenant notification worker binds to only its own sessions. After a
restart, reconcile with one approval::list-pending call.
Configuration
The whole config — runtime wiring and deployment approval defaults — lives
in the single engine configuration entry approval-gate (operator-edited
via the console's Configuration screen; reactive reload, no polling). There is
no committed config.yaml. On first boot the worker seeds the entry with
the built-in defaults (including rules) so the editor is pre-filled; existing
stored values are never overwritten except to add a missing rules field.
{
"default_mode": "manual", // manual | auto | full — sessions with no stored settings
"rules": [ // first match wins; no match → hold
"!approval::*",
{ "function": "state::get", "action": "allow", "modes": ["auto"] }
]
}configuration is a required boot dependency: the worker registers the
schema and fetches the authoritative value at startup, and a failed
register/fetch aborts boot (the gate must run on a known, authoritative policy
surface, never a guessed one). When no value is stored yet the built-in
defaults above are seeded and used. Every field hot-reloads on
configuration::set — nothing requires a restart.
Agent exposure
Deny all approval::* and configuration::* functions to in-run agents:
resolve would let an agent approve its own held calls, and the settings
RPCs are self-escalation (the gate's human_only_function rule is the
in-depth backstop). list_pending / get_pending are read-only and
redacted, but they enumerate held calls across sessions — keep them off
agent allow-lists too.
Local development & testing
cargo test # lib suites: pure unit + engine-backed handlers
cargo test --test integration # engine-backed; self-skips without `iii`
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
./target/debug/approval-gate --manifest # registry-publish manifestThe integration suite spawns a real engine (III_ENGINE_BIN or iii on
PATH) with configuration + iii-state, registers the production surface
in-process, and fakes sibling RPCs where noted (session::get). With the
harness binary available, tests/harness_integration.rs additionally boots the
real harness worker for cross-worker hold / sweep / resolve checks.
Architecture documentation
Deep documentation lives in architecture/: internals.md for maintainers (evaluation order, the emit-gate deletion mechanics, lazy seeding, engine facts the code depends on) and integration.md for consumers (the full function/trigger contract, the harness handoff, deployment notes, what not to do).
api reference (json)
{
"functions": [
{
"description": "Add a function to the session's auto-mode trust list (idempotent). Human/console-only.",
"metadata": {},
"name": "approval::add-always-allow",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"function_id": {
"type": "string"
},
"session_id": {
"type": "string"
}
},
"required": [
"function_id",
"session_id"
],
"title": "AlwaysAllowMutationRequest",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AlwaysAllowEntry": {
"properties": {
"function_id": {
"type": "string"
},
"granted_at": {
"description": "ms epoch",
"format": "int64",
"type": "integer"
},
"granted_by": {
"$ref": "#/definitions/GrantedBy"
}
},
"required": [
"function_id",
"granted_at",
"granted_by"
],
"type": "object"
},
"ApprovalSettings": {
"description": "The stored per-session record (scope `approval_settings`). Reads compute the effective settings from configuration defaults when no record exists — see settings.rs.",
"properties": {
"always_allow": {
"default": [],
"description": "Consulted only in auto mode.",
"items": {
"$ref": "#/definitions/AlwaysAllowEntry"
},
"type": "array"
},
"approved_always": {
"default": [],
"description": "Consulted in every mode — remembered human decisions.",
"items": {
"$ref": "#/definitions/AlwaysAllowEntry"
},
"type": "array"
},
"mode": {
"allOf": [
{
"$ref": "#/definitions/PermissionMode"
}
],
"default": "manual"
},
"mode_set_at": {
"default": 0,
"description": "ms epoch",
"format": "int64",
"type": "integer"
}
},
"type": "object"
},
"GrantedBy": {
"oneOf": [
{
"enum": [
"user_click"
],
"type": "string"
},
{
"description": "Copied from deployment auto-scoped allow rules on first mutation.",
"enum": [
"seed"
],
"type": "string"
}
]
},
"PermissionMode": {
"enum": [
"manual",
"auto",
"full"
],
"type": "string"
}
},
"description": "Shared by every settings mutation RPC: the post-mutation record.",
"properties": {
"settings": {
"$ref": "#/definitions/ApprovalSettings"
}
},
"required": [
"settings"
],
"title": "SettingsResponse",
"type": "object"
}
},
{
"description": "Record a per-session 'approve always' grant (honoured in every mode). Human/console-only.",
"metadata": {},
"name": "approval::approve-always",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"function_id": {
"type": "string"
},
"session_id": {
"type": "string"
}
},
"required": [
"function_id",
"session_id"
],
"title": "ApproveAlwaysRequest",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AlwaysAllowEntry": {
"properties": {
"function_id": {
"type": "string"
},
"granted_at": {
"description": "ms epoch",
"format": "int64",
"type": "integer"
},
"granted_by": {
"$ref": "#/definitions/GrantedBy"
}
},
"required": [
"function_id",
"granted_at",
"granted_by"
],
"type": "object"
},
"ApprovalSettings": {
"description": "The stored per-session record (scope `approval_settings`). Reads compute the effective settings from configuration defaults when no record exists — see settings.rs.",
"properties": {
"always_allow": {
"default": [],
"description": "Consulted only in auto mode.",
"items": {
"$ref": "#/definitions/AlwaysAllowEntry"
},
"type": "array"
},
"approved_always": {
"default": [],
"description": "Consulted in every mode — remembered human decisions.",
"items": {
"$ref": "#/definitions/AlwaysAllowEntry"
},
"type": "array"
},
"mode": {
"allOf": [
{
"$ref": "#/definitions/PermissionMode"
}
],
"default": "manual"
},
"mode_set_at": {
"default": 0,
"description": "ms epoch",
"format": "int64",
"type": "integer"
}
},
"type": "object"
},
"GrantedBy": {
"oneOf": [
{
"enum": [
"user_click"
],
"type": "string"
},
{
"description": "Copied from deployment auto-scoped allow rules on first mutation.",
"enum": [
"seed"
],
"type": "string"
}
]
},
"PermissionMode": {
"enum": [
"manual",
"auto",
"full"
],
"type": "string"
}
},
"description": "Shared by every settings mutation RPC: the post-mutation record.",
"properties": {
"settings": {
"$ref": "#/definitions/ApprovalSettings"
}
},
"required": [
"settings"
],
"title": "SettingsResponse",
"type": "object"
}
},
{
"description": "Drop the session's stored settings record (revert to configuration defaults).",
"metadata": {},
"name": "approval::clear-settings",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"session_id": {
"type": "string"
}
},
"required": [
"session_id"
],
"title": "ClearSettingsRequest",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"cleared": {
"type": "boolean"
}
},
"required": [
"cleared"
],
"title": "ClearSettingsResponse",
"type": "object"
}
},
{
"description": "pre_trigger hook: evaluate the permission model and answer continue / deny / hold; writes the pending inbox record on hold. Called by the harness only.",
"metadata": {},
"name": "approval::gate",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"HookCall": {
"properties": {
"arguments": {
"default": null
},
"function_id": {
"type": "string"
},
"id": {
"type": "string"
}
},
"required": [
"function_id",
"id"
],
"type": "object"
}
},
"properties": {
"call": {
"anyOf": [
{
"$ref": "#/definitions/HookCall"
},
{
"type": "null"
}
],
"description": "pre_trigger payload."
},
"depth": {
"default": 0,
"description": "Sub-agent depth (hooks run for child turns too).",
"format": "int64",
"type": "integer"
},
"metadata": {
"additionalProperties": true,
"default": null,
"description": "The per-send tracing metadata.",
"type": [
"object",
"null"
]
},
"point": {
"default": null,
"type": [
"string",
"null"
]
},
"session_id": {
"type": "string"
},
"step": {
"default": null,
"format": "int64",
"type": [
"integer",
"null"
]
},
"turn_id": {
"type": "string"
}
},
"required": [
"session_id",
"turn_id"
],
"title": "HookInput",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"oneOf": [
{
"properties": {
"decision": {
"enum": [
"continue"
],
"type": "string"
}
},
"required": [
"decision"
],
"type": "object"
},
{
"properties": {
"decision": {
"enum": [
"deny"
],
"type": "string"
},
"reason": {
"type": "string"
}
},
"required": [
"decision",
"reason"
],
"type": "object"
},
{
"properties": {
"decision": {
"enum": [
"hold"
],
"type": "string"
}
},
"required": [
"decision"
],
"type": "object"
}
],
"title": "HookOutput"
}
},
{
"description": "Read one pending record; null when resolved or unknown.",
"metadata": {},
"name": "approval::get-pending",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"function_call_id": {
"description": "The function call id of the held call.",
"type": "string"
},
"session_id": {
"description": "The session the pending record belongs to.",
"type": "string"
}
},
"required": [
"function_call_id",
"session_id"
],
"title": "GetPendingRequest",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"anyOf": [
{
"$ref": "#/definitions/GetPendingResponse"
},
{
"type": "null"
}
],
"definitions": {
"GetPendingResponse": {
"description": "`null` (handler returns `Option<GetPendingResponse>`) when resolved or unknown.",
"properties": {
"pending": {
"$ref": "#/definitions/PendingApprovalRecord"
}
},
"required": [
"pending"
],
"type": "object"
},
"PendingApprovalRecord": {
"description": "The inbox payload — shared by both triggers, `list_pending`, and `get_pending`. Self-describing (all ids inside the value) and notification-safe (arguments pass through redaction).",
"properties": {
"arguments_excerpt": {
"default": null,
"description": "Redacted — safe to forward to notification channels."
},
"assistant_excerpt": {
"description": "First text block of the assistant message that contained this function_call. Best-effort; not derivable from `pre_trigger` `HookInput` in v1, so always omitted today.",
"type": [
"string",
"null"
]
},
"depth": {
"default": 0,
"description": "Sub-agent depth (0 = top-level), from `HookInput`.",
"format": "int64",
"type": "integer"
},
"function_call_id": {
"type": "string"
},
"function_id": {
"type": "string"
},
"pending_at": {
"description": "ms epoch",
"format": "int64",
"type": "integer"
},
"session_description": {
"type": [
"string",
"null"
]
},
"session_id": {
"type": "string"
},
"session_metadata": {
"additionalProperties": true,
"description": "Tenancy + routing (trigger config filter target).",
"type": [
"object",
"null"
]
},
"session_title": {
"type": [
"string",
"null"
]
},
"turn_id": {
"type": "string"
}
},
"required": [
"function_call_id",
"function_id",
"pending_at",
"session_id",
"turn_id"
],
"type": "object"
}
},
"title": "Nullable_GetPendingResponse"
}
},
{
"description": "Read the session's effective settings (stored record or configuration defaults); never writes.",
"metadata": {},
"name": "approval::get-settings",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"session_id": {
"type": "string"
}
},
"required": [
"session_id"
],
"title": "GetSettingsRequest",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AlwaysAllowEntry": {
"properties": {
"function_id": {
"type": "string"
},
"granted_at": {
"description": "ms epoch",
"format": "int64",
"type": "integer"
},
"granted_by": {
"$ref": "#/definitions/GrantedBy"
}
},
"required": [
"function_id",
"granted_at",
"granted_by"
],
"type": "object"
},
"ApprovalSettings": {
"description": "The stored per-session record (scope `approval_settings`). Reads compute the effective settings from configuration defaults when no record exists — see settings.rs.",
"properties": {
"always_allow": {
"default": [],
"description": "Consulted only in auto mode.",
"items": {
"$ref": "#/definitions/AlwaysAllowEntry"
},
"type": "array"
},
"approved_always": {
"default": [],
"description": "Consulted in every mode — remembered human decisions.",
"items": {
"$ref": "#/definitions/AlwaysAllowEntry"
},
"type": "array"
},
"mode": {
"allOf": [
{
"$ref": "#/definitions/PermissionMode"
}
],
"default": "manual"
},
"mode_set_at": {
"default": 0,
"description": "ms epoch",
"format": "int64",
"type": "integer"
}
},
"type": "object"
},
"GrantedBy": {
"oneOf": [
{
"enum": [
"user_click"
],
"type": "string"
},
{
"description": "Copied from deployment auto-scoped allow rules on first mutation.",
"enum": [
"seed"
],
"type": "string"
}
]
},
"PermissionMode": {
"enum": [
"manual",
"auto",
"full"
],
"type": "string"
},
"SettingsSource": {
"enum": [
"stored",
"defaults"
],
"type": "string"
}
},
"properties": {
"settings": {
"$ref": "#/definitions/ApprovalSettings"
},
"source": {
"allOf": [
{
"$ref": "#/definitions/SettingsSource"
}
],
"description": "Whether a per-session record exists."
}
},
"required": [
"settings",
"source"
],
"title": "GetSettingsResponse",
"type": "object"
}
},
{
"description": "The pending inbox across sessions, with tenancy filters; the catch-up path for notification workers after a restart.",
"metadata": {},
"name": "approval::list-pending",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"cursor": {
"default": null,
"description": "Opaque.",
"type": [
"string",
"null"
]
},
"limit": {
"default": null,
"description": "Default 50.",
"format": "uint",
"minimum": 0,
"type": [
"integer",
"null"
]
},
"metadata": {
"additionalProperties": true,
"default": null,
"description": "Equality match against `session_metadata` (tenancy).",
"type": [
"object",
"null"
]
},
"session_id": {
"default": null,
"type": [
"string",
"null"
]
}
},
"title": "ListPendingRequest",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"PendingApprovalRecord": {
"description": "The inbox payload — shared by both triggers, `list_pending`, and `get_pending`. Self-describing (all ids inside the value) and notification-safe (arguments pass through redaction).",
"properties": {
"arguments_excerpt": {
"default": null,
"description": "Redacted — safe to forward to notification channels."
},
"assistant_excerpt": {
"description": "First text block of the assistant message that contained this function_call. Best-effort; not derivable from `pre_trigger` `HookInput` in v1, so always omitted today.",
"type": [
"string",
"null"
]
},
"depth": {
"default": 0,
"description": "Sub-agent depth (0 = top-level), from `HookInput`.",
"format": "int64",
"type": "integer"
},
"function_call_id": {
"type": "string"
},
"function_id": {
"type": "string"
},
"pending_at": {
"description": "ms epoch",
"format": "int64",
"type": "integer"
},
"session_description": {
"type": [
"string",
"null"
]
},
"session_id": {
"type": "string"
},
"session_metadata": {
"additionalProperties": true,
"description": "Tenancy + routing (trigger config filter target).",
"type": [
"object",
"null"
]
},
"session_title": {
"type": [
"string",
"null"
]
},
"turn_id": {
"type": "string"
}
},
"required": [
"function_call_id",
"function_id",
"pending_at",
"session_id",
"turn_id"
],
"type": "object"
}
},
"properties": {
"next_cursor": {
"type": [
"string",
"null"
]
},
"pending": {
"description": "Ordered by pending_at ascending.",
"items": {
"$ref": "#/definitions/PendingApprovalRecord"
},
"type": "array"
}
},
"required": [
"pending"
],
"title": "ListPendingResponse",
"type": "object"
}
},
{
"description": "Internal: hot-reload approval-gate from the authoritative configuration when it changes — swaps the per-call snapshot (timeouts + approval defaults).",
"metadata": {},
"name": "approval::on-config-change",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Internal `approval::on-config-change` trigger payload. The handler re-fetches the authoritative configuration, so this carries only the (advisory) configuration id; a struct (not `Value`) keeps the request schema concrete and unknown fields are ignored.",
"properties": {
"id": {
"default": null,
"description": "Configuration id that changed (advisory; the handler re-fetches the value).",
"type": [
"string",
"null"
]
}
},
"title": "OnConfigChangeEvent",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Ack returned by the internal `approval::on-config-change` handler.",
"properties": {
"ok": {
"type": "boolean"
}
},
"required": [
"ok"
],
"title": "OnConfigChangeResponse",
"type": "object"
}
},
{
"description": "Internal: session::deleted handler (purge the session's settings and pending records).",
"metadata": {},
"name": "approval::on-session-deleted",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "`session::deleted` payload (only the field we read).",
"properties": {
"session_id": {
"type": "string"
}
},
"required": [
"session_id"
],
"title": "SessionDeletedEvent",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Acknowledgement returned by the internal trigger-bound handlers (`on-config-change`, `on-session-deleted`, `on-turn-completed`) whose result is not consumed by callers — kept typed so the response schema is concrete rather than the permissive `AnyValue` (\"unknown\") schema a `Value` return would emit.",
"properties": {
"ok": {
"type": "boolean"
}
},
"required": [
"ok"
],
"title": "EventAck",
"type": "object"
}
},
{
"description": "Internal: harness::turn-completed handler (purge the turn's pending records).",
"metadata": {},
"name": "approval::on-turn-completed",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "`harness::turn-completed` payload (only the field we read).",
"properties": {
"turn_id": {
"type": "string"
}
},
"required": [
"turn_id"
],
"title": "TurnCompletedEvent",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Acknowledgement returned by the internal trigger-bound handlers (`on-config-change`, `on-session-deleted`, `on-turn-completed`) whose result is not consumed by callers — kept typed so the response schema is concrete rather than the permissive `AnyValue` (\"unknown\") schema a `Value` return would emit.",
"properties": {
"ok": {
"type": "boolean"
}
},
"required": [
"ok"
],
"title": "EventAck",
"type": "object"
}
},
{
"description": "Remove a function from the session's auto-mode trust list (no-op when absent). Human/console-only.",
"metadata": {},
"name": "approval::remove-always-allow",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"function_id": {
"type": "string"
},
"session_id": {
"type": "string"
}
},
"required": [
"function_id",
"session_id"
],
"title": "AlwaysAllowMutationRequest",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AlwaysAllowEntry": {
"properties": {
"function_id": {
"type": "string"
},
"granted_at": {
"description": "ms epoch",
"format": "int64",
"type": "integer"
},
"granted_by": {
"$ref": "#/definitions/GrantedBy"
}
},
"required": [
"function_id",
"granted_at",
"granted_by"
],
"type": "object"
},
"ApprovalSettings": {
"description": "The stored per-session record (scope `approval_settings`). Reads compute the effective settings from configuration defaults when no record exists — see settings.rs.",
"properties": {
"always_allow": {
"default": [],
"description": "Consulted only in auto mode.",
"items": {
"$ref": "#/definitions/AlwaysAllowEntry"
},
"type": "array"
},
"approved_always": {
"default": [],
"description": "Consulted in every mode — remembered human decisions.",
"items": {
"$ref": "#/definitions/AlwaysAllowEntry"
},
"type": "array"
},
"mode": {
"allOf": [
{
"$ref": "#/definitions/PermissionMode"
}
],
"default": "manual"
},
"mode_set_at": {
"default": 0,
"description": "ms epoch",
"format": "int64",
"type": "integer"
}
},
"type": "object"
},
"GrantedBy": {
"oneOf": [
{
"enum": [
"user_click"
],
"type": "string"
},
{
"description": "Copied from deployment auto-scoped allow rules on first mutation.",
"enum": [
"seed"
],
"type": "string"
}
]
},
"PermissionMode": {
"enum": [
"manual",
"auto",
"full"
],
"type": "string"
}
},
"description": "Shared by every settings mutation RPC: the post-mutation record.",
"properties": {
"settings": {
"$ref": "#/definitions/ApprovalSettings"
}
},
"required": [
"settings"
],
"title": "SettingsResponse",
"type": "object"
}
},
{
"description": "Apply a human decision to a held call: release it for execution (allow) or deliver a denial (deny). Human/console-only.",
"metadata": {},
"name": "approval::resolve",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"ResolveDecision": {
"enum": [
"allow",
"deny"
],
"type": "string"
}
},
"properties": {
"decision": {
"$ref": "#/definitions/ResolveDecision"
},
"function_call_id": {
"type": "string"
},
"reason": {
"default": null,
"description": "Surfaced to the model on deny.",
"type": [
"string",
"null"
]
},
"session_id": {
"type": "string"
}
},
"required": [
"decision",
"function_call_id",
"session_id"
],
"title": "ResolveRequest",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"resolved": {
"description": "false: unknown/already-resolved pending call.",
"type": "boolean"
},
"turn_resumed": {
"description": "Passthrough from harness::function::resolve.",
"type": [
"boolean",
"null"
]
}
},
"required": [
"resolved"
],
"title": "ResolveResponse",
"type": "object"
}
},
{
"description": "Set the session's permission mode (manual / auto / full). Human/console-only.",
"metadata": {},
"name": "approval::set-mode",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"PermissionMode": {
"enum": [
"manual",
"auto",
"full"
],
"type": "string"
}
},
"properties": {
"mode": {
"$ref": "#/definitions/PermissionMode"
},
"session_id": {
"type": "string"
}
},
"required": [
"mode",
"session_id"
],
"title": "SetModeRequest",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AlwaysAllowEntry": {
"properties": {
"function_id": {
"type": "string"
},
"granted_at": {
"description": "ms epoch",
"format": "int64",
"type": "integer"
},
"granted_by": {
"$ref": "#/definitions/GrantedBy"
}
},
"required": [
"function_id",
"granted_at",
"granted_by"
],
"type": "object"
},
"ApprovalSettings": {
"description": "The stored per-session record (scope `approval_settings`). Reads compute the effective settings from configuration defaults when no record exists — see settings.rs.",
"properties": {
"always_allow": {
"default": [],
"description": "Consulted only in auto mode.",
"items": {
"$ref": "#/definitions/AlwaysAllowEntry"
},
"type": "array"
},
"approved_always": {
"default": [],
"description": "Consulted in every mode — remembered human decisions.",
"items": {
"$ref": "#/definitions/AlwaysAllowEntry"
},
"type": "array"
},
"mode": {
"allOf": [
{
"$ref": "#/definitions/PermissionMode"
}
],
"default": "manual"
},
"mode_set_at": {
"default": 0,
"description": "ms epoch",
"format": "int64",
"type": "integer"
}
},
"type": "object"
},
"GrantedBy": {
"oneOf": [
{
"enum": [
"user_click"
],
"type": "string"
},
{
"description": "Copied from deployment auto-scoped allow rules on first mutation.",
"enum": [
"seed"
],
"type": "string"
}
]
},
"PermissionMode": {
"enum": [
"manual",
"auto",
"full"
],
"type": "string"
}
},
"description": "Shared by every settings mutation RPC: the post-mutation record.",
"properties": {
"settings": {
"$ref": "#/definitions/ApprovalSettings"
}
},
"required": [
"settings"
],
"title": "SettingsResponse",
"type": "object"
}
}
],
"triggers": [
{
"description": "A function call was held for human approval and its inbox record written. Payload: PendingApprovalRecord (redacted args, session context, expiry). Bind notification workers here.",
"invocation_schema": {},
"metadata": {},
"name": "approval::pending-created",
"return_schema": {}
},
{
"description": "A pending approval left the inbox (outcome: allow | deny | timeout | aborted). Emitted exactly once per record; lets UIs clear badges.",
"invocation_schema": {},
"metadata": {},
"name": "approval::pending-resolved",
"return_schema": {}
}
]
}