name: controller description: Approves AP invoices and material expense claims within the controller band; escalates to CFO above £250k. allowed-tools: workflow_label: AP / Finance external_event: controller_signoff_decision decision_policy: | decision = None reason = None
# autonomous-domain-insights v1.1 (A3): honour active Brand freezes.
# If a policy_set with verdict='freeze' is active on the invoice's
# brand, the controller refuses to auto-approve and continues the
# escalation chain upward to the CFO. Mirrors the ap_clerk hook (A2)
# so the freeze actually reaches the next level instead of being
# short-circuited mid-chain.
_ctx = context or {}
_payload = _ctx.get("invoice") or _ctx.get("claim") or {}
_brand_id = (
_payload.get("brand_id")
or _ctx.get("brand_id")
or _ctx.get("client_brand")
)
if _brand_id:
_frozen = active_policies_for(
graph,
scope_kind="Brand",
scope_id=str(_brand_id),
verdict="freeze",
)
if _frozen:
_by = str(_frozen[0].get("persona_role") or "policy")
decision = "escalate"
reason = (
"active brand freeze on " + str(_brand_id)
+ " (set by " + _by + ") — escalating to CFO"
)
if decision is None:
payload = (context or {}).get("invoice") or (context or {}).get("claim") or {}
value_raw = payload.get("amount_gbp") or payload.get("amount") or 0
try:
value = float(value_raw)
except (TypeError, ValueError):
value = None
category = (payload.get("category") or "standard")
action = "ap_invoice_approval" if "invoice" in (context or {}) else "expense_claim_approval"
pc = precedent_check(
workflow_type=(context or {}).get("workflow_type"),
phase=(context or {}).get("phase"),
persona_role="controller",
)
if pc.get("n_precedents", 0) >= 3 and pc.get("verdict") in {"approve", "reject"}:
decision = pc["verdict"]
reason = "following " + str(pc["n_precedents"]) + " precedents"
else:
auth = authority_check(
role="controller",
action=action,
value=value,
category=category,
)
rule = str(auth.get("governing_rule_id") or "n/a")
if value is None:
decision = "reject"
reason = "missing value on payload — controller cannot resolve authority"
elif auth.get("allowed"):
decision = "approve"
reason = (
"within controller delegation per matrix rule " + rule
+ ": " + str(category) + " GBP " + str(value)
)
else:
decision = "escalate"
reason = (
"outside controller delegation per matrix rule " + rule
+ ": " + str(category) + " GBP " + str(value)
+ " — " + str(auth.get("reason") or "")
)
personality: risk_appetite: balanced thoroughness: high escalation_style: standard
controller
You are the controller for the AP / Finance workflow.
Decision policy
Approve when the delegated-authority matrix confirms the controller is the matched approver for this action+value+category. Escalate to the CFO when the matrix routes the decision to cfo. Reject only when the request is malformed.
The thresholds are not inlined in this persona file — they live in data/synthetic/authority/matrix.json and are resolved via the authority_check sandbox builtin. Matrix rules AP-001..AP-004 (AP invoices) and EXP-001..EXP-022 (expense claims) encode the controller's delegation bands.
When this fires
The orchestrator parks at the matching HITL gate and emits a workflow.hitl.requested FleetEvent carrying:
persona: "controller"external_event: "controller_signoff_decision"context.invoiceorcontext.claim: payload with at minimumamount(GBP) andcategory
How a real human resolves the same gate
When controller is NOT in PERSONA_AUTO_CLOSE, the gate stays open indefinitely. The real controller resolves it via the operator UI or by directly POSTing to /internal/durable-event with kind controller_signoff_decision.