Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions docs/spec/instrument/specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,18 +314,20 @@ Tamper-evidence here is bounded by the signature in use. Under the HMAC baseline

## 9. Approver Model

ASK approvers MAY be human, agent, or service. `ask_details.approver = { type, id, endpoint }`. The Approver receives an ACS-shaped request and returns an ACS-shaped decision. Approver authentication is REQUIRED. Guardian MUST verify approver identity against policy.
ASK approvers MAY be human, agent, or service. `ask_details.approver = { type, id, endpoint, auth }`. The Approver receives an ACS-shaped request and returns an ACS-shaped decision.

Approver authentication is REQUIRED across all three approver types (`human`, `agent`, `service`), and the Guardian MUST verify the identity of the party returning an approval decision against deployment policy before honoring it. The optional `approver.auth` object (`method`: `bearer` or `mtls`) describes authentication for a machine (`agent` or `service`) approver endpoint; its presence on the wire does not by itself establish that verification occurred. A deployment authenticates a `human` approver out of band through its trusted approval interface and conveys the verified identity to the Guardian; a claimed `approver.id` alone is insufficient.

Single-hop only in v0.1. Approvers MUST NOT return ASK. Quorum and recursive ASK deferred to v0.2.

### 9.1 Intent extension via ASK (normative)

When a Guardian raises ASK because a request is outside `Intent.parsed`, the approver's grant MAY include an `intent_extension` field (see [`ask-details.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/ask-details.json)) containing capabilities to add to `Intent.parsed`. The extension's `scope` selects between `this_request` (capabilities apply only to the in-flight request) and `session` (capabilities are appended to `Intent.parsed` for the remainder of the session).

On `scope: session`, the Guardian MUST:
On `scope: session`, when and only when a verified approver grants the request, the Guardian MUST:

1. Append the capabilities to `Intent.parsed`.
2. Write a ContextEntry with `step_type: "intent_extension"` recording the approver identity, the granted capabilities, and the originating ASK's `step_id`.
2. Write a ContextEntry with `step_type: "intent_extension"` recording the verified approver identity, the granted capabilities, and the originating ASK's `step_id`.
3. Carry the extension's `provenance` forward distinct from the original `parser_provenance` so audits can distinguish parser-derived capabilities from approver-extended ones.

Intent extensions are subject to the session's `scope_mode`: a Guardian operating under `scope_mode: strict` MUST NOT honor extensions that would add capabilities the deployment policy forbids in strict mode. This mechanism is the only conformant path to mutate `Intent.parsed` after Intent is committed.
Expand Down
30 changes: 30 additions & 0 deletions tests/test_approver_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Approver rules in Specification §9 and ask-details.json (#51)."""

import json
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
SPEC = ROOT / "docs" / "spec" / "instrument" / "specification.md"
ASK = ROOT / "specification" / "v0.1.0" / "ask-details.json"


def approver_section():
text = SPEC.read_text(encoding="utf-8")
return text.split("## 9. Approver Model", 1)[1].split("## 10.", 1)[0]


def test_every_approver_type_must_authenticate():
section = approver_section()
assert "REQUIRED across all three approver types" in section
assert "the Guardian MUST verify" in section
assert "out of band" in section
assert "a claimed `approver.id` alone is insufficient" in section
assert "does not by itself establish that verification occurred" in section
assert "when and only when a verified approver grants" in section


def test_the_v0_1_0_wire_contract_is_unchanged():
"""A required `auth` breaks valid payloads, so issue #51 keeps it for a later spec version."""
schema = json.loads(ASK.read_text(encoding="utf-8"))
assert schema["properties"]["approver"]["required"] == ["type", "id"]
assert schema["properties"]["timeout_disposition"]["default"] == "deny"
Loading