Skip to content

Segregation of Duties

Applies to: All subscriptions

Purpose

Explain the maker-checker model, state precisely what changes when it is relaxed, and make clear whose decision that is. This setting is one of the first things an auditor asks about, and "it was easier during testing" is not an answer anyone wants to give.

Audience

The customer's finance control owner and internal audit, primarily. System administrators, who make the change. Implementation consultants, who must not make it unilaterally.

Prerequisites

Reference

The model

Segregation of duties separates preparation from commitment. The person who prepares a document is not the person who commits it.

Function Role Can
Maker Tax Executive Create, edit, correct, print, view
Checker Tax Controller Everything the maker can, plus generate, cancel, approve, issue, promote

The control exists because the acts on the checker's side are irreversible or externally visible: registering an invoice with the government, cancelling a registration, issuing a document, promoting a template that changes how every future document is interpreted.

Four further separations are built into the role set:

Separation Effect
Administration from transactions An org admin manages users and configuration but cannot commit a transaction
Logistics from tax Logistics roles hold the e-Way Bill family but cannot register invoices
Assurance from operations The auditor role can view and print, and nothing else
Sign-off from processing The Approver role holds invoice.approve and no transactional permission, so approval authority can be granted without processing authority

The two role axes

Approval in Complifly is governed by two independent fields on the user record, and both are required. Setting one without the other is the most common approval misconfiguration, and it is silent — no error, no log entry, no refused request.

System role Approval role
Stored in sys_user.role sys_user.approval_role_key
Governed by config/policies.js — the permission matrix dbo.mst_approval_role — the seniority ladder
Answers May this person call the approve endpoint? Which level of a multi-level approval are they?
Values that can approve Approver, Tax Controller (not Super Admin, which does not approve customer documents) Finance Executive, Finance Manager, Finance Controller, Business Head, Plant Head, Director, CFO
Missing → 403 on the approve call; no button in the UI The user is never named by any level, so nothing reaches them

The approval ladder

Seven levels, junior to senior. The rank matters: when several rules fire on one document, the levels they require are merged and ordered by rank.

Rank Approval role Typically
10 finance_executive AP/AR staff
20 finance_manager Team lead, routine value
30 finance_controller Owns the ledger; the usual second signature
40 business_head Commercial owner of the relationship
40 plant_head Same rank — a peer, not a further step
50 director Board-level sign-off
60 cfo Highest step

Assign both fields together, on Administration > Admin & Logs > Users & Roles. The ladder is also served by GET /api/admin/approval-roles for anyone provisioning users programmatically.

A level with no holder stalls silently

Nothing checks that somebody holds each approval role the matrix uses. If exactly one person holds finance_controller and they leave, every document routed through that level stops, with no alert. Assign each level used by your matrix to at least two people — any holder can clear it, and the history records which one did.

Controls the approval workflow enforces regardless of role

Control Behaviour
Self-approval Whoever submitted a document cannot decide it, whatever role they hold. Checked on identity, not role, and not waivable — not by an org admin, and a Super Admin does not hold the approve permission at all
Sequence Levels are decided in order. Level 2 cannot act before level 1 has
Administrative override An Org Administrator, who alone holds approval.override, may force-approve or force-reject, but must give a written reason, and every remaining level is stamped OVERRIDDEN rather than APPROVED — a bypass is legible as a bypass. It is refused on a document that administrator submitted (SELF_OVERRIDE), so a second person is always involved. Super Admin does not hold it

One decision for the company, locked when live work starts

Segregation of duties is decided once, for the whole company, at onboarding. The customer and Complifly agree the setting; onboarding records it against the company (with the reason, if it is relaxed) and the onboarding wizard never writes it per registration. There is no per-GSTIN setting: FEATURE_SCOPE_NOT_ALLOWED refuses one. Overrides that already existed when this rule shipped are kept and still apply, so nothing changed for anyone on the day; they can be removed, and no new one can be added.

The setting locks against relaxing when the company starts live work. The lock is read from the company's activity when someone tries to relax it, not stored, so no path can forget to engage it:

Counts as live work Does not count
An IRN issued Work on a registration whose GSP configuration is explicitly SANDBOX (the registration's own row first, else the company's)
An e-way bill generated A cancelled approval (a system act, not a decision)
An approval decided (approved or rejected) A pending approval

A company with no GSP configuration row at all has no recorded environment, so its work does count. The lock errs towards engaging: a wrongly locked company costs a Super Admin request; a wrongly open one is the drift the lock exists to stop.

Who and what Before live work After live work
Org Admin turns it on Allowed Allowed, no reason
Org Admin turns it off Allowed, with a reason; other Org Admins are told 403 SOD_LOCKED, nothing changed, the attempt is audited
Super Admin turns it off Allowed, with a reason Allowed with a reason and the customer's approval reference (approvalRef); 400 SOD_APPROVAL_REF_REQUIRED without it

Turning it on is never refused, because it takes rights away from makers and gives nobody any. The Operational Controls screen asks for confirmation first and says how many active Tax Controllers the company has: if there are none, nobody could approve, issue or cancel until one is added, and after the lock turning it off again needs Complifly.

Which direction a change goes is decided by each registration's effective value, not by which call was made. Removing an old GSTIN override that enforced, under a company default of off, relaxes that registration and is locked like any other relaxation. Resetting a relaxed company default to the platform default (on) tightens and is open.

The customer's approval reference is a record, not a check. It is a ticket number, an e-mail subject or a signed form number, 3 to 120 characters. Complifly stores it at the front of the reason in Audit History, writes it to the ACCESS_WIDENED entry as customerApprovalRef, and sends it to the customer's other Org Admins, with the statement that Complifly, not a company administrator, made the change. Keep the evidence it points to.

Relaxing it needs a reason, and your other administrators are told

You cannot relax segregation of duties without a written reason, and your company's other Org Admins are emailed when you do. This applies to every caller, a Complifly Super Admin included, and to every route that can relax it: setting the company default to off and removing an older override that was keeping a registration enforced while the company default is off.

What happens Detail
The request carries no reason 400 ACCESS_WIDENING_REASON_REQUIRED. Nothing is changed, no approval is cancelled, nobody is emailed
The request carries a reason The change is saved, the reason is stored in the control's Audit History, and an ACCESS_WIDENED entry is written to the customer's audit log
The notice One email to the company's other active Org Admins, naming who, what, which registrations, the reason, and what relaxing means. The person who made the change is not emailed. A bulk change sends one email per company, not one per registration
Nobody else to tell A company with a single Org Admin: the change still goes through and the entry records NO_OTHER_ADMIN
The email cannot be sent The change still stands and the entry records EMAIL_NOT_CONFIGURED or SEND_FAILED. The notice is best-effort so an email outage cannot block a reasoned change; the record never is
Tightening, or no effective change Switching it on, or writing "off" on a registration that is already off, needs no reason and sends nothing

This email is not the Attention Center's notification switch. It does not depend on NOTIFICATION_DISPATCH_ENABLED (which defaults off): a company must not be able to mute a notice that its own approval controls were relaxed. It only ever goes to that company's own Org Admins.

Everything done under the relaxation is marked. An audit entry written during a request that was permitted only because segregation of duties was relaxed for that registration carries accessBasis: SOD_RELAXED and the permissions that needed it (accessBasisPermissions). A Tax Controller, who holds the right either way, is not marked. That is how an auditor separates actions taken under the designed model from actions taken under the relaxation, without reconstructing what the setting was on that date. Entries from before 15 October 2026 carry no mark.

What relaxing it does

Setting SOD_ENFORCED to off collapses maker permissions into checker permissions. Specifically:

Role Gains, when relaxed
Tax Executive The full checker set — generate, cancel, approve, issue, promote
Org Admin The full checker set, adding transactional authority to configuration authority
Transport Manager, Logistics Operator The full checker set, including IRN generation
Approver Nothing. Deliberately excluded from the collapse

Note the breadth. Relaxation is not a narrow adjustment to one workflow: it grants transactional authority to roles that were deliberately built without it, including logistics and administration.

Approver is excluded on purpose. An approver who inherited the checker set would gain invoice.create and invoice.issue — the ability to raise a document and then sign it off, which is the single outcome the role was added to prevent. A tenant running in SME mode therefore keeps this one separation even though it has relaxed the others.

Two approval controls also survive relaxation entirely: the approval level must still match, and nobody can approve a document they submitted. Relaxation widens who can be asked to approve; it never lets a level be skipped.

When relaxation is legitimate

There is a real case for it. A small business where one person genuinely performs preparation and commitment — because there is no second person — gains nothing from a control that only produces a blocked screen. The setting exists for that situation.

It is not legitimate as:

  • A way past an approval step that is slowing down user acceptance testing
  • A default applied because nobody decided
  • A temporary measure at go-live that is never reviewed

The distinction is whether a control owner decided, with the consequence understood.

Whose decision it is

The customer's finance control owner, not the implementation team and not the system administrator who makes the change.

Record:

Record Why
Who decided An auditor will ask
When To establish the period affected
The business reason "No second person available" is a reason; "faster" is not
Compensating controls, if any Review of issued documents, for example
Review date Circumstances change as an organisation grows

If relaxation is not acceptable but staffing is thin

Options short of relaxing the setting:

Option Trade-off
One checker covering several areas Concentrates authority in one person, but keeps preparation separate
A checker outside the immediate team Slower, but preserves the control
Keep it enforced and name a second person A second user in the Tax Controller or Tax Executive role keeps the separation, at the cost of one more account
A separate company for the small entity The setting is decided per company, so an entity that genuinely cannot separate duties can sit in its own company with its own decision. This is an onboarding decision, not a switch
Relax with a compensating review Documented review of committed documents

The setting is no longer per registration: a group cannot keep one registration enforced and relax a sibling inside the same company. If that was how a group was set up before, the older override still applies and can be removed, but not replaced.

Validation

Check Method Pass condition
Current state known Check the setting per GSTIN Known for every registration, not assumed
Enforcement works With it on, sign in as a tax executive and attempt to generate an IRN Refused
Relaxation works as documented With it off in a test environment, repeat Permitted
Breadth understood With it off, check what a logistics operator can now do Confirm the customer intended to grant transactional authority to logistics
Decision recorded Change record Names the decision-maker, date and reason
Decided once for the company Operational Controls > Company Defaults, and the row's lock summary Known and recorded; the row says whether the company is locked, and since when
No older per-GSTIN override remains Operational Controls > GSTIN Overrides, or node scripts/sod_inventory.js Any left is deliberate and understood; none can be added
Compensating controls in place Where relaxed, confirm the agreed review is happening Evidenced, not merely planned
Review scheduled Operations calendar A date to revisit the decision

Troubleshooting

Symptom Cause Action
A maker cannot generate an IRN Segregation enforced. Working as designed Explain the model. Do not relax the setting to resolve a single ticket
A maker can suddenly generate an IRN Segregation relaxed Confirm this was a recorded decision. If not, re-enable and investigate who changed it
A logistics user can register invoices Relaxation collapsed their permissions into the checker set Expected effect. If unintended, this is the strongest argument for re-enabling
Auditor asks who authorised relaxation Look in the Audit Logs for ACCESS_WIDENED (who, when, which registrations, the reason, who was emailed) and in the control's Audit History Changes made before 15 October 2026 carry no reason; for those, the control's history shows who and when only
Approvals block a go-live Approval thresholds, not segregation Check the Invoice Generation thresholds first; they are a separate control
One registration behaves differently An older GSTIN override from before the setting became company-wide Confirm it is deliberate. Operational Controls > GSTIN Overrides shows it as an older override; it can be reset, not replaced
An Org Admin gets SOD_LOCKED when switching it off The company has started live work (a live IRN, a generated e-way bill or a decided approval) Expected. Turning it on is still open. To relax it, a Complifly Super Admin needs a reason and the customer's approval reference
A company still in UAT is already locked Its test work is not on a SANDBOX GSP configuration, or it has no GSP configuration row, so its work counts Record the test period's GSP configuration as SANDBOX before the first test document. The lock reads the current GSP configuration and each server remembers a "locked" answer until it restarts, so relabelling afterwards is not a reliable way to unlock; use the Super Admin exception
A Super Admin gets SOD_APPROVAL_REF_REQUIRED The company is locked and the request carried no usable approvalRef Get the customer's approval in writing and send its reference with the reason
Turning it on left nobody able to issue The company had no active Tax Controller Add one under Users & Roles. Turning it off again needs Complifly once the company is locked
Relaxed during testing and never reviewed The classic failure Put a review date in the reason you give when relaxing; it is recorded and emailed to the other administrators
"ACCESS_WIDENING_REASON_REQUIRED" when switching it off No reason was given Give one. See above
Customer wants partial relaxation for one action Not supported — relaxation is all or nothing for the company Consider a role change with the consequence accepted, or a second user in the checker role
An approver sees the workflow panel but no Approve button Their system role does not hold invoice.approve Assign the Approver system role (or Tax Controller). The approval role alone is not enough
An approver can approve, but no document ever reaches them No approval_role_key on their user record, or a different one Check both fields on the user. The panel names the role the document is waiting on
A document sits PENDING with nobody able to act The level names an approval role no active user holds Assign that approval role to someone. This is why levels want two holders
A senior approval appears in the history as OVERRIDDEN It was taken through Force Approve rather than by the named approver Expected if an administrator bypassed. If it is happening routinely, the approvers are not set up — fix the roles rather than normalising the override
A user cannot approve their own document Self-approval is blocked on identity and is not waivable Correct behaviour. Have another holder of the level decide, or have a different Org Administrator use Force Approve with a reason
Force Approve answers SELF_OVERRIDE You submitted this document, and Override refuses the person who submitted it Ask another Org Administrator. If you are the only one, a second administrator is needed
A Super Admin cannot approve or override anything Super Admin does not hold invoice.approve, tds.checker or approval.override: approving a customer's document is the customer's decision Correct behaviour. Use a customer role that holds the permission