ScopedOwnerGateManager
scripts/goal-runner/scoped_owner_gate.py defines the ScopedOwnerGateManager. This is the only path for creating, listing, approving, and consuming owner gates.
Construction
from scoped_owner_gate import ScopedOwnerGateManager
manager = ScopedOwnerGateManager(store)
Public API
manager.list_open(scope: str | None = None) -> list[dict]
manager.approve(gate_id, *, approver, source, note, evidence_path=None) -> dict
manager.reject(gate_id, *, approver, reason, evidence_path=None) -> dict
manager.consume(gate_id, *, run_id, subject_sha) -> dict
manager.reserve(gate_id, *, run_id) -> dict
Validation rules
_validate enforces:
scope ∈ VALID_SCOPESsource ∈ EXTERNAL_HUMAN_SOURCESsource ∈ APPROVAL_SCOPE_REQUIRES[scope]approveris not an agent-role identityMERGE_APPROVALdoes not acceptowner-cliPRODUCTION_PROMOTIONdoes not acceptowner-clisubject_shamatches the runtime HEAD (inconsume)
Subject binding
The consume call requires a subject_sha. If the runtime HEAD differs from gate.payload.subject_sha, consumption is refused.
Replay protection
After consume, the gate's dispatches_used is incremented. Subsequent consume calls on the same gate fail closed.
What the manager is NOT
- It does not store private keys.
- It does not verify signatures (that is
signed_ticket_verifier.py). - It does not authorize destructive operations itself (it only persists the gate).
The manager is the gate state machine, not the operation executor. The orchestrator consumes the gate and then performs the operation.