Business Units, Team Management, and RBAC: How Australian Enterprises Structure Carbon Accounting Across Divisions
ASX 200 groups rarely have one reporting boundary. Divisions, subsidiaries, joint ventures, sites: they all have to roll up into one AASB S2 disclosure and one NGER submission. This walks through how Carbonly's Business Units tree, six-role RBAC, and enterprise identity stack (SAML, OIDC, SCIM, MFA, session revocation) are built for that reality.
Most carbon accounting tools were built for a single company with a single set of sites. That model breaks the moment you look at a real ASX 200 group. There's a listed parent, half a dozen operating divisions, a handful of subsidiaries under different equity stakes, two joint ventures where the operator carries 100% of NGER but the parent only reports its equity share under AASB S2, and a consulting firm doing the assurance prep across three of the divisions.
One reporting boundary? There isn't one. There are dozens, and they roll up differently depending on which framework you're answering.
This post walks through how we built the Business Units module, six-role RBAC, and the enterprise identity stack (SAML, OIDC, SCIM, MFA, session revocation) to model that reality rather than paper over it.
Why a single-tenant "one big table" model fails ASX 200 reporting
The default shape a lot of tooling reaches for is: one organisation, one flat list of "sites" or "facilities", one set of users, one report. It's clean. It fits the demo.
It also collapses the second you try to answer a real question.
Take a diversified industrials group with a mining division, a construction division, and a 40% stake in a rail-freight JV where a Tier 1 contractor is the operator. Under NGER, the mining and construction facilities report at 100% because the group has operational control. The JV facilities are reported by the operator, not the group. Under AASB S2, the metrics pillar wants equity-share treatment for that same JV, so the group does pick up 40% of the JV's Scope 1+2 in its disclosure. Same underlying activity data. Three different sums.
If your carbon system stores emissions as "belongs to org 47" with no further structure, you cannot compute those three sums without rebuilding the boundary in a spreadsheet. Which is what most people are still doing.
The alternative is to make the boundary a first-class object in the data model. That's what the Business Units module is.
Business Unit tree: parent-child across divisions, subsidiaries, JVs
Every emission record in Carbonly is attached to a business unit. A business unit has a required parent pointer, so the whole organisation forms a tree up to three levels deep: top-level divisions or regions, mid-level departments or facilities, and low-level teams or sub-projects.
Each business unit carries the fields that regulators actually care about:
- Code and name (unique per organisation)
- ANZSIC code (drives NGER threshold determination, because mining, manufacturing and generation have different rules)
- Australian state from the AU_STATES dropdown (NSW, VIC, QLD, WA, SA, TAS, ACT, NT), required because the grid emission factor for Scope 2 is state-based
- Address, suburb, GPS coordinates, time zone
- Manager (a user)
- Start and end dates for project-lifecycle units
- A unique inbound email address per business unit for document forwarding into that unit's inbox
The tree matters because emissions aggregate upward. A user viewing a top-level division sees the sum of every descendant. A user viewing the group sees the sum of every division. And the dashboard's project filter is hierarchy-aware. Pick a division and every widget re-runs against that division plus all its children.
For an ASX 200 group with 40 sites across three divisions, that means one place to look, three natural sums, and no manual re-aggregation.
The six roles: Owner, Admin, Manager, Contributor, Auditor, Viewer
Every user has a role at the organisation level and (optionally) a different role at each business unit they're assigned to. The hierarchy is:
owner > admin > manager > contributor > auditor > viewer
Six roles rather than the three or four a generic permission model would give you. The six exist because they map to six real jobs.
Owner is the tenant super-admin. Owners are the only role that can transfer ownership, delete the organisation, or grant Owner or Admin. There must always be at least one owner. The last owner cannot be demoted or removed.
Admin runs the tenant day-to-day. Admins invite users, create business units, configure integrations, manage the material library, and approve emission data. They can grant Manager, Contributor, Auditor and Viewer roles, but not Owner or Admin. That's Owner-only.
Manager runs a division or a site. They only see the business units they've been explicitly assigned to. They can invite team members within their assigned projects, approve emission data on those projects, and manage carbon plans for what they own. They cannot touch org-wide settings or invite people to projects they don't run.
Contributor is the persona everyone forgets. Site managers, project engineers, warehouse leads, the people doing the bulk of data entry. Contributors can create and edit emissions and documents on the projects they're assigned to. They cannot manage the team, cannot change settings, cannot see the Team menu at all. They exist because in a multi-site organisation the "data entry user who is not a manager" is usually the largest group of licence holders, and giving them the Manager surface is a security liability.
Auditor is a first-class read role plus one specific write path (see the next section).
Viewer is truly read-only. Executives, board members, investor-relations folks who want to see the numbers but should never touch them.
What each role can and cannot do
Roles map cleanly onto the write-permission gates in the system. Here's the actual matrix.
| Action | Owner | Admin | Manager | Contributor | Auditor | Viewer |
|---|---|---|---|---|---|---|
| Delete organisation, transfer ownership | Yes | No | No | No | No | No |
| Grant Owner/Admin role | Yes | No | No | No | No | No |
| Invite users, manage team, edit org settings | Yes | Yes | No | No | No | No |
| Configure integrations, edit material library | Yes | Yes | No | No | No | No |
| Create/edit business units | Yes | Yes | No | No | No | No |
| Invite users to assigned projects | Yes | Yes | Yes | No | No | No |
| Create/edit emissions and upload documents on assigned projects | Yes | Yes | Yes | Yes | No | No |
| Confirm AI extractions | Yes | Yes | Yes | Yes | No | No |
| Verify, reject, lock or restate a reporting period | No (except via role change) | No (except via role change) | No | No | Yes | No |
| Read emissions, reports, dashboards on assigned projects | Yes | Yes | Yes | Yes | Yes | Yes |
Vendor-side lifecycle actions (archive an org, schedule deletion, suspend, unsuspend) sit outside the six-role tenant matrix altogether. They're gated to a separate platform-admin role with a 15-minute step-up authentication window, and every action is audit-tagged as vendor-initiated. Tenants can't accidentally delete themselves; support can't quietly do it either.
The architectural point worth flagging: manager, auditor and viewer are all project-scoped by default. Their auth.me response returns an explicit projectIds array, and every list query, single-record fetch, dashboard aggregate and export applies that filter server-side. Owner and admin get projectIds: null, which is the flag for "full access, don't filter". A manager physically cannot see emissions from a division they haven't been assigned to. The SQL doesn't return them.
Auditor: a dedicated role for external assurance
This is the role a generic "read-only user" model cannot express. External assurance providers (Big 4, mid-tier firms, boutique climate risk shops) need read access to everything in the reporting scope plus a very specific set of writes.
Under ISAE 3410, the international assurance standard that AASB and audit firms follow for GHG statements, the auditor cannot be the author of a record they audit. They can never create an emission from scratch or edit one. But they must be able to mark a record as verified, reject a confirmed line, lock a reporting period once it's closed, and restate a locked period when a material error is found.
That's four writes. In Carbonly they all flow through one channel, auditorWriteProcedure, the single legal write path for the auditor role. Every write through that path produces a paired audit-log entry stamped with the auditor's identity, so the assurance partner can trace the entire chain of authorship from raw invoice through confirmation through verification through period lock.
Practically, the auditor persona lands on the Auditor Workspace when they log in. Bulk Verify and Bulk Reject are one click. The Evidence Pack export gives them a zip of source documents, extraction confidence scores, calculation trace, and period-lock history. Those are the artefacts they need for their working papers.
Every email CTA sent to an auditor lands on the Auditor Workspace regardless of the org's AI autonomy mode. Auditors don't confirm extractions; giving them a "Confirm now" button would be an ISAE 3410 problem.
The upshot: your external assurance provider gets a workspace built for the job they actually do, with the evidence assembled in the platform rather than requested from your finance team by email.
Enterprise SSO: SAML 2.0, OpenID Connect, SCIM 2.0
Once you get past 100 seats, your identity team is not going to accept another vendor with its own username-password store. That's a security committee "no", and correctly so.
Carbonly's enterprise identity layer supports:
- SAML 2.0 for organisations on Okta, Azure Entra ID, PingFederate or any SAML-compliant IdP
- OpenID Connect for the same IdPs where OIDC is the preferred protocol
- SCIM 2.0 for automated user provisioning and deprovisioning
Each tenant configures its own IdP. When SSO is on, the login form redirects to the configured provider. No password, no local account. The IdP owns identity; Carbonly owns authorisation. This is Owner-only to configure, because handing SSO to an admin who doesn't run the identity team is how tenants lock themselves out.
SCIM matters more than most buyers realise. Manual user provisioning ("send me a spreadsheet of who to invite") does not scale, and neither does manual deprovisioning. When someone leaves the group and IT deactivates them in Entra, SCIM propagates the deactivation into Carbonly within minutes. No one is manually chasing a list of ex-employees to revoke access.
The bearer token that authorises SCIM is shown once on creation and stored hashed. If it's rotated, in-flight SCIM creates fail with a 401. Existing SCIM-provisioned users are not deactivated automatically on token revocation. That's a deliberate decision, because auto-deactivating everyone the moment a token is rotated would be catastrophic.
New SCIM users default to the viewer role. Elevation is Owner-only. That's the correct-by-default posture: a mis-configured SCIM push cannot accidentally create a hundred admins.
MFA with security step-up on high-risk actions
Time-based one-time password (TOTP) MFA is available for every user, enrolled at /settings/security/mfa. Backup codes are generated once at enrolment and shown once, the usual pattern.
The wrinkle that matters: disabling your own MFA requires a fresh MFA challenge. A logged-in user cannot flip MFA off without re-entering a current TOTP code. That closes the "attacker has an active session, quietly disables MFA, keeps the session forever" attack path.
Step-up authentication also gates the vendor-side platform-admin surfaces. Even if a support engineer is authorised to run a cross-tenant lifecycle action, they cannot run it from a normal session. They need a fresh authentication within a 15-minute elevation window, and every action they take during that window is stamped in the audit trail with the impersonation context.
Session revocation and token versioning
There's one more identity primitive that matters when something goes wrong: the ability to invalidate every active session for a user in one API call.
Every issued session token includes a version number tied to the user record. When something bad happens (a laptop is lost, credentials are suspected of being phished, an admin is offboarded), the user or their admin calls auth.revokeAllSessions. That bumps the user's tokenVersion. Every existing session becomes invalid on the next request because its embedded version number no longer matches. The user re-authenticates on their trusted device and carries on; every other session is dead.
This is a self-service action for the user. It doesn't require support to intervene, and it doesn't require waiting for a session timeout. Combined with SCIM deprovisioning for the offboarding case, session revocation is the last line of defence for the "we need this person out, now" scenario.
JV consolidation flowing through the Business Unit tree
The JV problem is where the tree structure earns its keep. Each business unit can be linked to one or more joint ventures via businessUnitJvLinks, with a consolidation method: equity_share, operational_control, or financial_control, the three NGER-aligned methods.
An equity-share link stores an ownership percentage. When Reports.generate runs, it applies a per-row equity multiplier as a SQL CASE WHEN:
emissions_tco2e * COALESCE(jv_ownership_pct, 100) / 100
That means the same underlying emission row produces different sums depending on which surface is asking:
- The Dashboard's "Total emissions" widget shows the full-share, org-wide number (tenants want to see their absolute footprint)
- The Dashboard's JV Emissions card shows the equity-adjusted number
- The NGER Section 19 report shows 100% per facility, per CER guidance (registered facilities count at 100% regardless of ownership share on an operational-control basis)
- The AASB S2 §29 metrics pillar shows the equity-share number
- The custom-report builder applies equity adjustment via the same CASE WHEN
- Carbonly Co-Pilot returns both
equityAdjustedSumandrawSumso the person asking can pick
JV partners themselves are tenant-scoped directory entries. There is no cross-tenant partner picker, because a shared picker would expose tenant names across the boundary. Each partner carries name, ABN, industry, contact email, ownership percentage, role, and optional evidence URL. Those fields support NGER Act s.22XK evidence retention and AASB S2 §B36 partner identification.
For a group with three JVs where they're the equity partner on two and the operator on one, this is the difference between one integrated view and three spreadsheets stitched together at year-end. We go deeper on the accounting mechanics in our posts on JV emissions reporting under equity-share vs operational control and operational, financial and equity-share consolidation under AASB S2.
The consultant workspace pattern
The other multi-org pattern that matters is consulting. A boutique climate consultancy running assurance prep for six mid-market clients does not want six separate logins, six separate password managers, and six separate contexts to switch between.
Carbonly's model: one consultant workspace holds N client organisations, each fully isolated at the tenant boundary. The consultant's user account has memberships in each client tenant, each with its own role assigned by the client. The organisation switcher in the sidebar flips context; the last-active org is remembered between sessions.
Data is never shared across tenants. Cross-org reads return NOT_FOUND (never FORBIDDEN) so a bad actor cannot even enumerate tenant IDs to see which ones exist. The multi-tenant isolation is enforced at every read and write through the same requireOrganization(ctx) gate, with a battery of end-to-end tests guarding the invariant.
This is important because we take a specific view on the consultant question: they are buyers, not competitors. A consultant using Carbonly across six clients scales their practice. Carbonly is the workshop equipment; they're the craftsperson doing the strategic work: materiality assessment, scenario analysis, board briefings, transition planning. The platform handles the plumbing so their hourly time goes into the analysis that clients actually pay for.
One honest gap here worth naming: cross-org reporting (a consultant wanting to benchmark Client A against Client B in a single view) is not something we do, deliberately. That would require sharing data across the tenant boundary, which we won't do. If the consultant needs benchmarks, they build them in their own workspace using anonymised extracts, the way they do today.
FAQ
How many levels deep can the business unit tree go? Three: top-level (divisions, regions, subsidiaries), mid-level (departments, facilities, projects), and low-level (teams, sites, sub-projects). Three is a deliberate ceiling, chosen to cover the group structures we see in published ASX 200 annual reports without letting the tree get so deep that hierarchical filtering becomes unintelligible.
Can one user have different roles in different business units? Yes. A user might be a Manager on the WA mining division, an Auditor on the QLD construction division, and a Viewer at the group level. Organisation-level owner and admin always outrank business-unit roles, so an org owner has full access regardless of what the BU-level assignment says.
How does Carbonly handle the "auditor cannot author" rule under ISAE 3410?
The auditor role has no create-emission or edit-emission path anywhere in the system. Their four legal writes (verify, reject, lock a period, restate a period) all flow through auditorWriteProcedure, and each writes a paired audit-log entry stamped with the auditor's identity. This is what separation of duties looks like in the data model, not just in the org chart.
What happens if SSO breaks and we can't log in? Every tenant retains at least one Owner account that can log in with local credentials as a break-glass path. We do not recommend making SSO the only login method for the last Owner account, and the Owner-only IdP configuration guardrail exists to prevent an admin from accidentally locking the whole tenant out.
How does licensing work across business units and joint memberships? One licence per user per organisation. A user with memberships in multiple organisations (a consultant, a group employee working across subsidiaries) shares a single licence seat rather than consuming one per org. Owners see total, used and available licences in the Licence Management dashboard. Per-project pricing is layered on top of the base $100/month platform fee.
The tree, the six roles, the identity stack: none of this is glamorous. It's plumbing. But it's the plumbing that determines whether an ASX 200 group can actually run its carbon accounting in one system rather than six, and whether an assurance provider spends their engagement testing your controls or reassembling your evidence.
If you're a CIO or Head of Sustainability looking at this problem for a real group structure, the practical starting point is a 45-minute walkthrough of your org chart and a mapping of who needs which role on which business unit. That's the conversation to start. Reach out to hello@carbonly.ai.
Related Reading
- Carbon Accounting Data Governance for Australian CIOs: SSO, SCIM, Audit Trail, and Australian Data Residency
- JV Emissions Reporting: Equity-Share vs Operational Control
- Operational, Financial and Equity-Share Consolidation Under AASB S2
- Joint Venture Emissions Allocation
- Carbon Accounting Audit Trail and Version Control: Seven-Year Retention
- Emission Factor Versioning and Audit Trail
- Joint Venture Carbon Reporting for Infrastructure Projects in Australia