orchestrator-present-approval · git:20260820.bd8af38 · 2026-08-20 · sha256 7dcf711af8e7d4b6
orchestrator-present-approval git:20260820.bd8af38A
Immutable. This exact content is served forever at /api/v1/blob/7dcf711af8e7d4b6.
--- name: orchestrator-present-approval description: Use when presenting a returned APPROVAL_REQUEST for informed user consent --- # Present Approval — Orchestrator Branch Present trusted contract data exactly. Do not execute, derive, shorten, reorder, or silently expand it. ## The surface is neutral; the mechanism that carries it is the adapter's Two things are separate and must stay separate. WHAT the user sees is one host-neutral surface: rendered from the sealed payload, identical on every host, with its exact shape, field set, render order and absence semantics stated in `template.md`. HOW that surface reaches the user -- the host's structured decision primitive, the form of the control the user selects, and the channel by which the selection becomes a grant -- belongs to the adapter for the host you are running under, and is documented in a skill named `<host>-consent-adapter`. Load that skill alongside this one before presenting; this skill does not restate its mechanics, and a host mechanism named here would be wrong on every other host. ## The activation channel is structured, and it must carry the approval id **Consent activates through the host's structured decision surface, never through free text.** The selection the user makes must carry the approval id -- its full value or its leading hex characters, depending on what the adapter's channel transports -- because that identifier is what the hook layer resolves to the pending row before creating the grant (`extract_nonce_from_label` -> `activate_db_pending_by_prefix`, `hooks/modules/security/approval_grants.py`). If the identifier does not reach that channel, nothing activates: no grant is inserted, the ledger stays `PENDING`, and every retry of the blocked command re-blocks on the same `approval_id` -- indistinguishable from a decision never having been made, while the user believes they consented. That is the incident this skill exists to prevent, and it is exactly as reachable on a single command as on a COMMAND_SET. The exact form the identifier must take, and the failure modes specific to it, are the adapter skill's -- read it there rather than guessing a shape here. **One decision activates one approval.** Present one approval per decision. The reason is presentation hygiene, not activation loss: a host event that answers several signed labels now activates every one of them, so grouping no longer drops a grant. What grouping still costs is the user's ability to read what they are signing -- several exact commands folded into one decision is one signature over a surface nobody consented to field by field. How many a single host interaction can carry is an adapter property, stated by the adapter skill. Before dispatching execution, confirm with `gaia approvals show <approval_id>` that the approval you intend to execute actually left `pending`. ## Singular vs COMMAND_SET presentation The question body is not composed here: it is the surface rendered from the sealed payload, and `template.md` states its exact shape, field set, render order and absence semantics. A singular request and a COMMAND_SET share that one shape -- the same indexed `COMMANDS (N)` block carries one command or many -- so there is no second layout to choose between and no field to decide about. Show it verbatim. One approval control covers the whole set, never one per command. Do not call a COMMAND_SET atomic: consent is grouped, execution is separate, ordered, and fail-fast. Do not claim verification has happened; this is the pre-execution consent point, and the surface's `VERIFICATION` field states what to check afterwards. Approval activation verifies the REQUESTED fingerprint. Presentation must still be exact because informed consent depends on what the human sees. If the contract is incomplete, reordered, mismatched, or ambiguous, do not repair it; route back to the producer. ## Who activates, who executes If the user approves, the orchestrator dispatches a fresh owning specialist with the grant context and `execution` skill; the orchestrator never runs the commands itself. `gaia approvals approve` is a separate, CLI-only admin verb that writes the DB directly and does **not** create a hook-side grant -- it is not the activation path, and it is not available to the orchestrator: the trusted-CLI role guard (`hooks/modules/security/gaia_cli_only_guard.py`, `EXPLICITLY_DENIED_PHRASES`) categorically denies `approvals approve` / `revoke` / `reject` / `reject-all` / `clean` / `replay` for the orchestrator role, non-approvable -- the orchestrator may only *read* approval state (`approvals list` / `show` / `pending` / `history` / `stats`, in `ALLOWED_READ_PHRASES`). Reads are the orchestrator's; approval decisions are not -- they happen exclusively through the decision the user makes on the host surface.