Dual control and obligations¶
Most authorisation decisions are yes or no. Some are "yes, provided that": the payment may go, provided a second person signs it; the export may run, provided it stays inside a budgeted session and the file is redacted first; the message may be sent, provided the recipient is on the approved list and it carries an Authorisation Stamp. The Xybern Authorisation Layer expresses these as obligations attached to an allowed action, and as dual control when the condition is a second, independent person.
Obligations¶
A mandate compiles to an obligation primitive whenever the outcome is conditional on something that must hold rather than something that is forbidden:
{"type": "obligation", "action_types": ["payment"], "decision": "allow_with_obligations",
"conditions": {
"when": {"operator": "AND", "rules": [{"field": "amount", "operator": ">", "value": 10000}]},
"obligations": [
{"type": "approved_recipient", "recipients": ["acc-ops-1", "acc-ops-2"]},
{"type": "budgeted_session"},
{"type": "declared_intent"},
{"type": "stamp"},
{"type": "notification", "to": "treasury@bank"},
{"type": "redaction", "fields": ["national_id"]}]}}
| Obligation | Kind | Satisfied by |
|---|---|---|
co_authorisation |
precondition | a second, independent person (see dual control) |
consent |
precondition | an independent agent the mandate names, sealed as consent, never as approval |
budgeted_session |
precondition | the action runs inside an active session that carries budgets |
approved_recipient |
precondition | every recipient on the action is on the rule's list or the workspace's approved recipients |
declared_intent |
precondition | the action was declared first and executes with its commitment |
stamp |
precondition | an Authorisation Stamp is issued with the decision |
notification |
postcondition | an attestation that the named party was notified (a webhook event is also sent) |
redaction |
postcondition | an attestation that the content was redacted before execution |
Preconditions are checked when the action is authorised, before any execution commitment is issued. A failed precondition refuses the action with the reason ("recipient not on the approved list"). A co-authorisation or consent holds the action. Postconditions are discharged afterwards by attestation:
r = client.intercept("export_report", metadata={"rows": 300, "to": "auditor@bank"}, agent_id="reporter", session_id=session_id)
r["outcome"] # allow_with_obligations
r["obligations"] # each with status, description, who satisfied it and when
client.attest_obligation(r["decision_id"], "redaction", evidence={"redact_job": "rj_91ab", "fields": ["national_id"]})
client.get_obligations(r["decision_id"])["all_satisfied"]
The decision string stays allow, escalate or block, so existing clients keep working. The outcome says allow_with_obligations, and the response, the lineage and the receipt carry the obligation list (authorisation.obligations), each entry with status, satisfied_by and satisfied_at. Every attestation, approval and consent is sealed to the Provenance Vault. Pre-flight reports obligations under "what it would take": allowed, provided that: every recipient on the approved list; run inside a budgeted session.
API: GET /v1/enforce/obligations/<decision_id>, POST .../attest ({type, evidence}), POST .../consent ({agent_id, reason}), GET /v1/enforce/co-authorisations.
Dual control¶
A dual_control primitive holds the action for a second pair of eyes:
{"type": "dual_control", "action_types": ["payment", "wire_transfer"], "decision": "allow_with_obligations",
"conditions": {"rules": [{"field": "amount", "operator": ">", "value": 50000}],
"independent_of": ["requester", "owner"], "mfa": true, "expires_seconds": 3600}}
The rules, enforced when the escalation is resolved and never in the agent's hands:
- The co-authoriser is a person. An agent cannot approve a dual-control hold, and an API key is never a person's MFA, so an MFA-required co-authorisation is approved from the dashboard.
- Independence. The approver must be a different person from the requester (
metadata.requested_by, and never the acting agent) and, whenowneris listed, from the accountable Charter signer on whose authority the agent acts.co_authoriserscan name who may approve. - MFA step-up. With
mfa: truethe approver's session must have verified MFA; otherwise the approval is refused withmfa_required. - Expiry. The hold expires after
expires_seconds(default four hours); an expired hold cannot be approved and the action must be requested again. - Sealed. The request, the approval or rejection, the approver, the MFA state and the reason are sealed to the Provenance Vault; the receipt's outcome reads authorised after dual control.
The Escalations view marks these holds with a Dual control pill and the rule that applies; an approval that breaks the rules fails with the reason. Decision D3 stands: an agent may only satisfy a consent obligation, and only when a mandate explicitly names it; that is sealed as consent and shown as consent, never as an approval.
Compliance notes¶
Dual control implements segregation of duties for agent actions (SAMA CSF 3.3.5, NCA ECC 2-2-3): the person who requested or is accountable for the action cannot be the one who co-authorises it, and the record proves who did. Obligations make "allowed with conditions" auditable: every condition, its status and who discharged it sits on the receipt.