Skip to main content

SOX — Sarbanes-Oxley Act (US Public Company Accounting Reform)

Scope: financial reporting controls of US publicly-traded companies. Centres on section 302 (officer certifications), section 404 (internal-control attestation), section 409 (material-change disclosure), section 802 (records retention), section 906 (criminal penalties for fraudulent certification).

The annotation is compliance: [SOX] on type, axonstore, axonendpoint, shield, flow. SOX is almost always combined with SOC2 and (in banking) PCI DSS.

Declaring SOX

type GeneralLedgerEntry compliance [SOX] {
period: AccountingPeriod,
account: ChartAccount,
debit: Money,
credit: Money,
counterparty: Counterparty,
approver: ApproverRef
}

axonstore GeneralLedger
compliance: [SOX, SOC2]
backend: postgresql
isolation: serializable
encryption: at_rest
retention: 7y # section 802 — 7-year retention for accounting records
on_breach: raise

axonendpoint PostJournalEntry {
flow: PostJournalFlow
method: POST
route: "/v1/ledger/journal-entries"
compliance: [SOX]
}

What the compiler enforces statically

SOX sectionAXON enforcement
section 302 — management certificationOut of scope (process).
section 404(a) — internal control structureA SOX-tagged axonendpoint whose method: is POST/PUT/DELETE requires a bound mandate: (the typed approval primitive) referencing an approval authority.
section 404(b) — segregation of dutiesA flow whose body posts a SOX-tagged ledger entry requires the mandate: approver to differ from the flow's invoking persona (statically checked: the approver identifier must not be the same as the as: persona).
section 409 — material change disclosureOut of scope (process).
section 802 — records retentionA SOX-tagged axonstore requires retention: ≥ 7y (retention: 7y, 10y, permanent). The type checker rejects shorter retention.
section 906 — criminal penalty for false certificationOut of scope (process).

What the runtime enforces

SOX sectionAXON runtime enforcement
section 404(a) — control evidenceEvery mutation to a SOX-tagged store emits an audit row with (actor, role, action, target, before_hash, after_hash, approver, timestamp).
section 404(a) — control effectivenessThe audit chain is hash-linked + signed; any post-hoc modification breaks the chain head — the v1.19.1 FIPS-friendly hash makes tampering detectable.
section 802 — records retentionThe runtime refuses purge on a SOX-tagged store before the declared retention has elapsed; even authorised purges emit a structured retention_break: event for review.
section 404(b) — SoD enforcementA mandate: whose approver matches the requesting persona at runtime is rejected with sod_violation:; this catches the case where static identifier equality wasn't enough (e.g. role-bound principals).

What you still attest manually

  • Officer certifications (section 302, section 906) — quarterly + annual.
  • Material weakness assessment — auditor judgment.
  • Walkthroughs of significant controls.
  • PCAOB / external audit opinion.
  • Whistleblower channels (section 301) — operational.
  • Change-management sign-offs around code that touches a SOX-tagged store.

Segregation of duties — the AXON model

section 404(b) demands that the person initiating a financial transaction cannot be the same person who approves it. AXON's mandate: field on a SOX-tagged endpoint encodes this:

mandate ManagerApproval
requires: capability("ledger.approve")
excludes_requester: true # AXON enforces requester ≠ approver
on_breach: raise

axonendpoint PostJournalEntry {
flow: PostJournalFlow
method: POST
route: "/v1/ledger/journal-entries"
compliance: [SOX]
mandate: ManagerApproval
}

The runtime resolves the requester's subject from the OIDC token, the approver's subject from the mandate carrier, and rejects the posting if they match — emitting an audit row tagged sod_violation_blocked.

Common patterns

Pattern 1 — Audit-trail query (section 404 evidence pack)

flow QueryAuditForSOX(period: AccountingPeriod) -> List<AuditRow> {
step Query {
given: period
retrieve: AuditLog where tag = "SOX" AND period = period
output: List<AuditRow>
}
return Query.output
}

The result feeds the section 404 management attestation evidence pack.

Pattern 2 — Retention-aware purge

flow CleanupOldEntries(cutoff: Date) {
step Inventory {
given: cutoff
retrieve: GeneralLedger where posted_at < cutoff
output: List<RowRef>
}
for row in Inventory.output {
step CheckRetention {
given: row
ask: "Has the section 802 7-year retention elapsed for this row?"
output: Bool
}
if CheckRetention.output {
step Purge {
given: row
purge: GeneralLedger
}
}
}
}

The runtime double-checks the retention even if the flow's logic gets it wrong — defence in depth.

Pattern 3 — Quarterly close lockdown

mandate QuarterlyCloseLock
requires: capability("ledger.posting")
period_lock_after: quarter_close
on_breach: raise

axonendpoint PostJournalEntry {
flow: PostJournalFlow
method: POST
route: "/v1/ledger/journal-entries"
compliance: [SOX]
mandate: [ManagerApproval, QuarterlyCloseLock]
}

Once the quarter closes, posting requires an explicit override approval at a higher capability tier.

When NOT to use SOX

  • Private companies not subject to SEC reporting. The audit trail still has value, but the SOX-specific retention + SoD constraints can be relaxed.
  • Operational metrics not part of the financial reporting scope. compliance: [SOC2] alone is usually sufficient.
  • Subsidiary entities wholly inside a non-public holding company. Check with counsel.

For combined SOX + PCI scenarios (banking, fintech), declare both: compliance: [PCI_DSS, SOX, SOC2]. Each framework's static and runtime checks compose independently.