Controls & audit
Accounting software is a system of controls, and most of them are enforced in the UI: segregation of duties, approvals bound to a version, an append-only audit trail, and evidence attached wherever an auditor will ask for it.
1. The control principles#
| Principle | What it means | How the UI enforces it |
|---|---|---|
| Segregation of duties | The person who prepares is not the person who approves | Enforced, never a setting. The approve action is absent, not disabled, for the preparer. |
| Authorization | Spend above a threshold needs a specific role | Thresholds shown at entry, not after submission |
| Completeness | Everything that happened is recorded | Gapless numbering; unmatched queues that cannot be dismissed |
| Accuracy | Recorded amounts are right | Three-way match, tie-outs, balance checks |
| Validity | Recorded things actually happened | Required source documents |
| Cutoff | Recorded in the right period | Period states, cutoff warnings |
| Immutability | History cannot be rewritten | Reverse, never edit or delete |
| Traceability | Every number traces to its source | Drill-down at every level |
Where the principles come from#
The list above is the working form of two frameworks. Almost every US registrant runs its annual ICFR assessment under SOX §404(a) against COSO's Internal Control — Integrated Framework (2013), which has five components (control environment, risk assessment, control activities, information and communication, monitoring) and seventeen principles. The auditor's side is PCAOB AS 2201.
The assertions a control is designed to address are the auditor's vocabulary for the same rows: existence or occurrence, completeness, valuation or allocation, rights and obligations, and presentation and disclosure (PCAOB AS 1105). The AICPA's AU-C 315 breaks out accuracy, cutoff and classification separately.
Control failures are graded, and the UI uses the grade instead of a generic "issue":
| Grade | Meaning | Consequence |
|---|---|---|
| Control deficiency | A control does not allow timely prevention or detection of misstatements | Tracked and remediated |
| Significant deficiency | Less severe than a material weakness, yet merits the audit committee's attention | Communicated to those charged with governance |
| Material weakness | A reasonable possibility that a material misstatement will not be prevented or detected on a timely basis | Disclosed; ICFR is not effective |
2. The audit trail#
Every state change on every financial object writes an immutable record.
type AuditEvent = {
id: string;
at: Date; // server time, UTC, never client time
actor: { id: string; name: string; role: string; onBehalfOf?: string };
action: string; // "posted" | "approved" | "reversed" | "reopened" | …
objectType: string;
objectId: string;
before?: unknown; // full prior state for changed fields
after?: unknown;
reason?: string; // required for privileged actions
source: "ui" | "api" | "integration" | "system";
requestId: string; // correlates a multi-object action
ipAddress?: string;
};
UI rules:
- The audit trail is a first-class view on every object, not a hidden admin log.
AuditTrailrenders as a timeline with actor, action, and diff. - Field-level diffs, not "record updated". "Amount changed from $1,240.00 to $1,420.00" is the useful form.
- Server time, UTC, timezone displayed. A client-supplied timestamp is not evidence.
onBehalfOfis rendered explicitly. Support staff and delegates acting for a user must look different from the user acting for themselves.- The audit trail is append-only and cannot be filtered into invisibility. Filters narrow the view and say so: "Showing 12 of 148 events."
- Privileged actions require a reason: period reopen, write-off above threshold, approval override, bank detail change, permission grant.
3. Approvals#
type Approval = {
step: number;
requiredRole: string;
approver?: { id: string; name: string; at: Date };
status: "pending" | "approved" | "rejected" | "voided";
reason?: string;
onVersion: string; // the object hash approved
};
onVersion is the critical field. An approval applies to a specific version of the object. Edit it afterwards and every approval voids and must be re-obtained, with the change shown to the re-approver. Editing after approval is the classic fraud path, and binding the approval to a version closes it.
UI rules:
- The chain is visible before submission: who, in what order, what's done, what's blocking.
- Rejection routes back with a reason, visible to the preparer.
- Delegation expires. Permanent delegation is a control failure, so the UI requires an end date.
- The approver sees the evidence inline: the source document, the match, the coding. An approver who has to click through to see what they're approving will eventually stop clicking.
ApprovalInboxis mobile-first. Approvals happen on phones between meetings, so the document has to be readable at 390px.
4. Roles#
A small set, each role a job someone holds. Products that ship twenty granular permissions end up with everyone as admin.
| Role | Can |
|---|---|
| Viewer | Read statements and transactions. No export of PII. |
| Preparer | Create drafts, code transactions, prepare schedules. Cannot post or approve. |
| Accountant | Post entries, reconcile, roll schedules. Cannot approve payments or close periods. |
| Controller | Approve entries, close periods, set materiality. Cannot pay. |
| Treasurer | Approve and release payments. Cannot post entries. |
| Admin | Users, roles, integrations. Cannot post, approve, or pay. |
| Auditor | Read everything including the audit trail. Write nothing, ever. |
The two structural separations: Controller and Treasurer are separate because the person who records an obligation should not be the person who settles it. Admin cannot transact, because whoever grants permissions should not be able to use them.
5. Evidence#
type Evidence = {
kind: "invoice" | "receipt" | "contract" | "statement" | "memo" | "screenshot";
file: Document;
uploadedBy: string; uploadedAt: Date;
hash: string; // content hash — proves it hasn't changed
linkedTo: { objectType: string; objectId: string }[];
};
UI rules:
- Required on bills, expenses, adjusting entries, and reconciliations. Those are the four places an auditor will ask for it.
- The content hash is stored and displayed. A document that changed after it was attached is a finding, and the hash is how you catch it.
- Evidence is viewable inline, never download-only.
EvidencePanelrenders PDFs and images beside the object. - Evidence is never deleted, only unlinked, and the unlink is audited.
6. Data integrity checks#
These run continuously. A failure surfaces as an exception, never as an automatic correction.
| Check | Assertion |
|---|---|
| Entry balance | Debits = credits, every entry |
| Accounting equation | Assets = Liabilities + Equity, every period |
| Subledger tie | AR aging = AR control; AP aging = AP control |
| Schedule tie | Every schedule closing = its GL account |
| Roll-forward continuity | Opening = prior closing, every schedule |
| Cash tie | Cash flow ending cash = balance sheet cash |
| Retained earnings | Opening RE + NI − distributions = closing RE |
| Intercompany | IC receivables = IC payables across entities |
| Numbering | Invoice and entry numbers gapless |
| Period integrity | No postings in closed periods |
| Orphan detection | No entries referencing deleted accounts or dimensions |
A failing check takes over its surface. The screen's main content becomes the failure, the difference and the path to investigate it, where other products would put a badge in a corner. A balance sheet that doesn't balance is broken, and the screen should say so first.
7. Auditor mode#
A read-only view built for the annual audit, because the alternative is exporting everything to spreadsheets and emailing them.
- Read-only, enforced on the server, not by hiding buttons.
- Scoped to a period and an entity set, with the scope displayed and locked for the session.
- Sampling. Select N transactions by a stated method (random, monetary-unit, judgmental) and record the seed so the selection can be reproduced.
- Evidence bundles. For any selection, export the entries, their source documents, their approvals and their audit trail as one package with a manifest.
- Auditor activity is itself audited, and the client can see it.
8. Privacy and retention#
- No monetary amount in any analytics event, log line, or error report. Events carry the shape of an interaction, never the amount.
- PII in exports is role-gated, and the export is audited with its scope.
- Retention depends on the jurisdiction, commonly 7 years and sometimes 10. Records inside retention cannot be deleted by any role, including admin.
- Deletion requests (GDPR and equivalents) conflict with retention obligations. The UI supports pseudonymization of the person while retaining the transaction, and states which was applied.
- Copy-to-clipboard copies raw values, and clipboard actions on sensitive fields are audited.