Skip to content

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, when owner is listed, from the accountable Charter signer on whose authority the agent acts. co_authorisers can name who may approve.
  • MFA step-up. With mfa: true the approver's session must have verified MFA; otherwise the approval is refused with mfa_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.