Sheet MER-11 — Audit & Compliance Manual
Auditor Portal
Bounded external access, auditor collaboration, and the model Meridian uses to share review material without losing control.
Scope
External auditors need visibility, but they do not need unrestricted access to the operating system. This guide focuses on the public-safe collaboration model rather than the private implementation surface.
This guide covers managing external auditor access to Meridian audit cycles — inviting auditors, monitoring their activity, generating evidence packages, and revoking access when needed.
Permissions required:
Meridian.auditto create invites, revoke invites, and generate evidence packages.
Inviting Auditors
Creating an Invite
- Navigate to the audit cycle detail page (the relevant workflow).
- Click the Auditor Invites tab.
Revoked and expired invitations remain immutable audit history. Use Re-issue to open a new invitation prefilled with the prior auditor, firm, role, and permissions. Meridian generates a new one-time code; it never restores or reveals the old credential. 3. Click Create Invite to open the invite form. 4. Fill in:
- Email: the auditor’s email address (used for OTP verification during acceptance).
- Name: the auditor’s display name (shown in the portal header).
- Firm (optional): the auditing firm.
- Role: Lead Auditor, Staff Auditor, or Observer. Roles are informational — they do not affect what the auditor can access. Permissions control access.
- Permissions (checkboxes, at least one required):
view_controls— show the overview Controls card, control list/detail, and walkthrough reads where applicable. It is not required for the overview landing page.view_evidence— download individual evidence files from control detail, not evidence packages.submit_irl— create information request list items and view the IRL workflow.download_package— read package metadata and download the latest published evidence package ZIP.view_findings— use the findings workspace, open finding detail, and append comments without draft finding creation.sign_off_walkthrough— record or update the auditor’s sign-off conclusion on a SOX walkthrough. This is a write/attestation action and is separate fromview_controls, which only grants read access to walkthrough detail.manage_walkthrough_samples— save, lock, supersede, or delete SOX walkthrough sample drafts from the portal. Sample reads still requireview_controls.view_report— read the report workspace without authority to sign off or close the engagement.- Expiry: defaults to 90 days from now, hard cap at 365 days.
- Click Create. The one-time invite code appears in a copy-to-clipboard modal. Use Open auditor portal to continue in the same browser tab to the acceptance form with the code and email prefilled. The explicit same-tab handoff prevents operators from mistaking the still-open internal audit workspace for the external portal. The acceptance page immediately removes the sensitive URL fragment from browser history after reading it.
The invite code is shown exactly once and cannot be retrieved later. Copy it immediately. If lost, revoke the invite and create a new one.
What the Auditor Receives
The public acceptance response never reveals whether an invite matched, whether
its tenant chain is valid, or whether delivery succeeded. In the authenticated
invite list, inspect the latest OTP delivery status, attempt/completion times,
attempt count, safe failure code, diagnostic, and retry boundary. Expand the
are worker-owned states; asking the auditor to resend does not create another
or email transport, then have the auditor explicitly request verification again
after the cooldown. Delivery unknown means Meridian cannot prove whether the
provider accepted the message and therefore will not retry it automatically.
A failed or unknown resend does not invalidate an earlier delivered code.
Seeing whether the auditor has actually started
The invite table’s Last active column shows the auditor’s most recent portal request, or Never signed in if they have not yet used the invite. That is the fastest way to tell “the code never arrived” apart from “the code arrived and they have not got to it yet”.
returns everything that invite has done in the portal — page views, evidence downloads, IRL submissions, findings, sign-off — newest first.
An invite stays usable for its whole window. The auditor can sign in again after an idle timeout, from a second device, or after clearing cookies, and each attempt sends a fresh code. You only need to issue a new invite once the existing one has expired or you have revoked it. The status column reflects that: Accepted means “signed in and still able to”, and an invite that has passed its expiry reads Expired even if the auditor used it earlier.
Permission Guidelines
The audit overview shows NavCards only for the workflows the invite unlocks. view_controls is required for the Controls surface, but it is not a blanket prerequisite for every portal destination.
| NavCard | Required permission |
|---|---|
| Controls | view_controls |
| Information Requests | submit_irl |
| Evidence Package | download_package |
| Report & Closeout | view_report |
| Findings | view_findings |
Typical full-suite permission sets by role:
| Role | Recommended permissions |
|---|---|
| Observer | view_controls |
| Findings read/comment Auditor | view_findings |
| Findings creator | view_findings, create_findings |
| Walkthrough sign-off-capable Auditor | sign_off_walkthrough |
| Walkthrough sample-capable Auditor | view_controls, manage_walkthrough_samples |
| Report viewer | view_report |
Audit Firms
An AuditFirm is an account-scoped record that assigns a name and optional email domain to a recurring audit engagement firm (e.g. “Deloitte”, “KPMG”). Registering a firm enables two capabilities:
- Invite linking — invites can be associated with a firm record so all invites for the same firm are discoverable together.
Audit firms are per-tenant: each client account owns its own firm rows. Two clients that both engage Deloitte hold separate AuditFirm rows — there is no platform-global firm directory.
Creating a Firm
domain is optional but recommended — it enables auto-linking. The domain is stored lower-cased.
Updating a Firm
All three fields are optional and independently updatable — only fields present in the request body are changed. Each changed field is recorded as audit_firm.updated with a before/after diff.
Managing Firm Members
Firm members are the expected email addresses for auditors at that firm. They are informational metadata — they do not gate invite creation, but they are available for future tooling.
Deleting a Firm
A firm cannot be deleted while any live (non-revoked, non-expired) invite still links to it. Revoke or wait for expiry of those invites first.
Note: Operator-side UI for firm management is not yet available. Use the API directly.
Managing Invites
Viewing Invites
The Auditor Invites tab on the audit detail page shows a paginated table of all invites for the cycle. Each row carries a status badge (frontend/src/components/audit/AuditInvitesTab.tsx) — there is currently no status-filter control on this tab; the table always shows every invite for the cycle:
| Badge | Meaning |
|---|---|
| Green (active) | Invite is live and can be accepted |
| Blue (accepted) | Auditor has accepted and verified via OTP |
| Red (revoked) | Invite was explicitly revoked by an operator |
| Yellow (expired) | Invite passed its expiry without being accepted or revoked |
The table shows: email, name, firm, role, permissions, created date, and expiry date.
Revoking Access
Click the Revoke button on any active or accepted invite. Revoking an invite:
- Sets the invite to revoked status immediately.
- Writes audit log entries for both the revoke action and the session sweep.
A revoked invite cannot be un-revoked. Issue a new invite if the auditor needs access again.
Generating Evidence Packages
When to Generate
Generate an evidence package when you are ready to share the full audit evidence set with external auditors. Typical timing: after all control reviews are rendered and before or during the reporting phase.
How to Generate
- On the audit detail page, scroll to the Evidence Package panel.
- Click Generate package for a foreground build or Generate in background for a durable background build.
-
The backend walks the materialized
ControlReviewscope — reviews, tests, executions, evidence, and findings — and builds a deterministic ZIP with a canonical v2 manifest. The panel shows the server-owned scope/state preview and every blocker before generation. -
Reopening the audit detail page resumes polling for active jobs. Completed jobs become the latest package row.
What is Included
The generated ZIP contains:
- Evidence files — one directory per evidence artifact, keyed by evidence ID.
If the scope is valid but incomplete, Meridian creates an internal Draft package. Only a Published package has passed review, evidence hash and freshness, test-execution, and finding-finality checks. An empty scope is a configuration error and cannot generate a package.
The manifest carries a SHA-256 manifest_hash that auditors reference in their workpapers. This hash is the package’s identity — it proves the auditor is looking at the exact same data you generated.
Idempotent Regeneration
Re-generating a package when the underlying data has not changed returns the existing package (same manifest_hash). Adding new evidence, recording new test executions, or updating reviews between generations produces a new package with a new hash. The old package remains in storage.
Size Limits
The package generation enforces hard caps: 10,000 evidence artifacts or 2 GiB bundle size. Hitting either cap returns a structured 413 error. Type 2 cycles with high-frequency connector snapshots are the most likely to hit these limits.
Async Generation for Large Cycles
- On the audit detail page, click Generate in background.
- Meridian queues a durable async job and shows its current state in the Evidence Package panel.
- If the job is already running when you reopen the page, the panel resumes polling automatically and shows the latest heartbeat / lease state.
- If the job fails, inspect
error_codeanderror_detail, fix the cause, then use Retry to create a fresh background job. - If the job is no longer needed, use Cancel. Pending jobs cancel immediately; running jobs are marked cancel-requested and stop only after the worker observes cancellation.
- On
completed, the newly generated package becomes the latest package row in the panel.
Sharing Packages
The portal displays the complete manifest SHA-256 value so the auditor can use the page as an integrity-verification surface. Metadata failures identify the failed dependency and provide a retry action. A ZIP download failure reports the specific failure without discarding the already loaded package metadata, so the auditor can retry safely. Package states introduced by a newer server are shown explicitly as unknown with the original value preserved.
Before issuing a link, enter the intended recipient. The recipient must be an active internal user or use an external domain configured in Reporting → Recipient Policy. Review the displayed scope/state hashes and acknowledge that the credential is bearer access: the recipient email is audit metadata, not cryptographic identity binding. Meridian rechecks publication state, manifest integrity, audit drift, and recipient policy when issuing and using the token. Regenerate a drifted package; issue new v2 access after a cutover retirement.
Monitoring Auditor Activity
Last Accessed Tracking
Every authenticated portal request updates last_accessed_at on both the session and the invite. The invite list shows when each auditor last accessed the portal.
Refreshes and new tabs use the existing HttpOnly portal cookie to restore the current engagement, permissions, expiry, and auditor display identity. No portal authority is stored in localStorage or sessionStorage, and opening a second tab does not require accepting the invite again while the cookie remains valid. A temporary bootstrap failure keeps the requested page in place and offers Retry. If an authenticated request instead returns 401, the portal immediately removes the stale engagement and permission context, returns the auditor to invite acceptance, and remembers the interrupted portal destination. After a replacement invite is verified for the same audit, the portal resumes that destination; a different audit or an unsafe destination falls back to Engagements.
This activity update is committed as part of authentication without interrupting
the request: Meridian retains the validated authorization snapshot while it
Audit Log
Meaningful state-changing actions are recorded in the audit log:
| Event | What happened |
|---|---|
auditor.invite.created |
Operator created an invite (email hash, role, permissions logged) |
auditor.invite.otp_delivery_queued / otp_delivery_delivered / otp_delivery_failed / otp_delivery_ambiguous |
Durable OTP queue, handoff, safe failure, or unknown outcome |
auditor.invite.accepted |
Auditor successfully accepted an invite and established a session |
auditor.invite.revoked |
Operator revoked an invite (killed session count logged) |
auditor.session.logout |
Auditor explicitly logged out |
auditor.evidence.download_requested / retrieval_verified / stream_started / downloaded / *_failed |
Evidence retrieval attempt, verification, stream start, completed stream, or safe failure outcome with one durable attempt ID |
auditor.irl.submitted |
Auditor submitted an information request (subject prefix logged) |
auditor.package.download_requested / retrieval_verified / stream_started / downloaded / *_failed |
Package retrieval and stream lifecycle with one attempt ID and manifest hash |
sox_walkthrough.sample.upsert |
Auditor or operator saved a walkthrough sample draft |
sox_walkthrough.sample.locked |
Auditor or operator locked the current sample and stamped the sample hash |
sox_walkthrough.sample.superseded |
Auditor or operator superseded a locked sample with a new draft |
sox_walkthrough.sample.deleted |
Auditor or operator deleted the current draft sample |
Privacy: email addresses are never written to the audit log in plaintext. A SHA-256 hash prefix is used for correlation.
Information Request List (IRL)
When auditors submit information requests through the portal, they appear in the standard evidence request inbox for the control under review.
Seeing Auditor Requests
Portal-originated evidence requests are distinguishable by their requested_by value (negative numbers indicate portal users). They follow the same open -> fulfilled | cancelled lifecycle as internal requests.
Fulfilling Requests
Fulfill auditor requests the same way you fulfill internal requests: upload or link evidence from the control’s program. The auditor sees the updated status on their next portal visit.
The auditor-side inbox uses server pagination and a persisted page size, so the full request history remains reachable. Creating a request requires a subject (up to 500 characters), description (up to 5,000 characters), and an in-scope control selected through server search. Control-picker failures and list-load failures expose retry actions. A failed submission keeps the subject, description, and selected control in the open form so the auditor can retry without reconstructing the request.
What Auditors See
Multi-Engagement Landing (Auditor Side)
After a successful OTP verify, the auditor lands on the relevant workflow — the multi-engagement landing page. This page fetches all active (non-revoked, non-expired) invites for the authenticated auditor email across all client accounts.
- Single engagement: the page auto-redirects to the relevant workflow immediately, preserving the existing single-cycle UX. There is no extra click for auditors working one engagement at a time.
- Multiple engagements: the page renders one card per client account, with a table of engagement rows showing cycle name, program, period, role, and status. The row for the current session’s invite is badged Current.
Email-based discovery: cross-account aggregation is keyed on AuditorInvite.email == authenticated_email. The session cookie proves the cookie holder controls that email (the OTP delivery channel). No auditor can see invites issued to a different email address.
Navigation: the portal header shows an Engagements link at all times so an auditor deep inside one engagement can return to the landing page without logging out.
Audit Overview
Summary page with cycle name, dates, framework, program, and counts: total controls, reviewed, with findings, open/closed findings. The auditor’s identity (name, firm, role) and granted permissions are displayed. The overview cards are the primary navigation surface. Each card is shown only when the auditor’s invite holds the permission that card requires:
The overview maps the audit lifecycle, auditor role, and granted permissions to explicit operator-facing labels. Values introduced by a newer server are shown as unknown with the original value preserved; they are never silently presented as a trusted known state. A failed overview request identifies the dependency failure and provides an explicit retry action.
| NavCard | Shown when invite has |
|---|---|
| Controls | view_controls |
| Information Requests | submit_irl |
| Evidence Package | download_package |
| Report & Closeout | view_report |
| Findings | view_findings |
| My Activity | always shown |
Control List
Paginated table of all controls in scope for the cycle. Searchable by control ref and title. Filterable by review status (not started / in review / reviewed / exception). Each row shows evidence and findings counts.
Search, filtering, page navigation, and page size are all sent to the server; the browser does not reconstruct or truncate the audit scope. A failed initial load has an explicit retry. If a refresh fails after controls are visible, the last successful page remains on screen with the dependency error and an inline retry action.
Control Detail
Full control information: description, objective, framework requirement mappings, test definitions with last run results, evidence artifacts collected within the audit window, and the review conclusion with reviewer notes.
Evidence counts and detail rows use Meridian’s evidence-control binding table. If an evidence artifact has been re-bound to a different in-scope control, the auditor portal follows that binding and does not rely on older direct control IDs left on the evidence row.
Evidence Download
Individual evidence files can be downloaded. Each download verifies the file’s SHA-256 hash against the stored hash (corruption detection) and writes an audit log entry. If a download fails, the control detail stays visible and identifies the evidence-download failure so the auditor can retry without losing context.
Evidence Window
Auditors only see evidence collected within the cycle’s date window (start_date to end_date). Evidence from before or after the audit period is excluded. This is a SOC 2 audit-chain requirement.
Report & Closeout
The Report & Closeout NavCard is shown on the Audit Overview when the invite
creating an invite automatically includes view_report; granting read access
never grants either mutation. On the page, sign_off_report enables Sign Off
enables Close Engagement** (kept disabled until report sign-off exists). A
view_report-only auditor sees the complete report workspace with neither
action.
If the cycle is still planning or fieldwork, or the displayed assurance
summary has blockers, sign-off remains disabled and the API rejects direct
attempts. Finish reviews, attach current hashed evidence, execute required
tests, finalize findings, and move the cycle into reporting before asking the
external auditor to attest. The confirmation displays the scope, state, and
report-content hashes being frozen.
The report workspace loads the audit overview and report snapshot as separate dependencies. If either request fails, the error identifies the failed dependency and Retry reloads both so the sign-off and closeout state cannot be assembled from mismatched snapshots. Assurance blockers remain visible to the auditor, but internal Meridian remediation routes are intentionally not linked from the external portal; the compliance contact must resolve those items in the internal audit workspace. A failed sign-off or closeout submission keeps the entered note and modal open for correction and retry. Unrecognized audit, finding, or MAP status values are shown as explicit unknown values rather than being silently omitted or presented as a known state.
Viewing External Auditor Attribution from the Internal UI
After an external auditor has signed off the report or closed the engagement via the portal, compliance managers and internal operators can see this attribution directly from the audit detail page.
On the Overview tab of an audit cycle, when sign-off or engagement close actions have been recorded by an external auditor:
- Signed off — displays the date the report was signed off and the email address of the auditor invite that performed the sign-off.
- Engagement closed — displays the date the engagement was closed and the email address of the auditor invite that performed the close.
This information appears in an External auditor sign-off card beneath the status guidance section. The card is hidden when neither action has been recorded (i.e., for internal audit cycles or cycles where the external auditor has not yet reached sign-off/close).
This closes the evidence-attribution gap for SOC 2 audit records: compliance managers no longer need to cross-reference the audit log to determine which external auditor closed the engagement.
Findings (T-05.12)
The New Finding control selector searches the invited audit cycle. If the control directory fails, the selector shows the dependency error and an in-place retry instead of presenting an empty directory as if the audit had no controls. Entered title and description text remain in the form while the selector recovers.
Walkthroughs and Sign-Off (T-05.12)
Auditors with view_controls see a Walkthroughs card on the audit overview.
Open it to reach the engagement’s walkthrough list, then choose View on a
row to inspect its steps, conclusion, transaction description, and current
sample. The list remains server-paginated; use its page controls rather than a
deep link to reach later walkthroughs. A session without view_controls does
not see the overview card and is denied if it enters the list URL directly.
Auditors with sign_off_walkthrough can sign off a walkthrough in In
Progress or Reviewed status by submitting a conclusion narrative — this
is a distinct write/attestation permission, so a read-only view_controls-only
invite cannot sign off. Sign-off is idempotent (re-signing updates the
conclusion).
The walkthrough list uses the server pagination envelope and exposes page-size and page navigation controls, so engagements with more than 100 walkthroughs remain fully discoverable. A failed sign-off keeps the conclusion text in the form and reports the API error for safe retry. Unknown control-review, test, evidence-kind, and walkthrough status values are rendered as explicit unknown states rather than silently normalized into trusted labels.
My Activity (T-05.13)
The My Activity tab shows the auditor’s personal action timeline for the engagement: page views, IRL submissions, finding changes, walkthrough sample changes, evidence and package downloads, report sign-off, and engagement closure. The timeline is newest-first and uses server pagination, including a persisted page-size selector, so it does not truncate the engagement to the first 100 or 200 records. Each row includes the server-provided detail when present. Unknown action values remain visible with their original value instead of being relabeled as a known action. This is the auditor’s workpaper trail and the operator’s breach-investigation source.
Session Hardening
External-auditor portal sessions enforce four hardening properties (PX-0115 / U-004):
- Idle timeout — 2 hours. A session whose
last_accessed_atis older than 2 hours is rejected with the same generic 401 as any other session-failure mode. Auditors stepping away for lunch will need to re-authenticate. - Absolute lifetime — 7 days by default, capped at the parent invite’s expiry (operator-configurable at invite creation; invites default to a 90-day expiry and are hard-capped at 365 days — in
routers/auditor_invites.py). - IP and User-Agent binding — both values are required at session creation and stored in non-null columns. They are compared on every subsequent request; a missing value or a cookie replayed from a different network or browser is rejected. Auditors who switch networks (laptop on Wi-Fi → mobile hotspot, VPN reconnect) or whose browser auto-updates the User-Agent string mid-session will need to re-authenticate.
Common Issues
| Symptom | Cause | Resolution |
|---|---|---|
| Auditor cannot accept invite | Invite expired, already accepted, or revoked | Check invite status in the invites tab; create a new invite if needed |
Verification delivery status is ambiguous |
A worker lease expired or transport raised after handoff began, so provider acceptance cannot be proven | Do not mark it delivered or force an automatic retry. Check provider records using the displayed timestamps; if delivery cannot be reconciled, have the auditor explicitly request a new verification after cooldown. |
Verification delivery status is failed |
The invite became ineligible, payload integrity failed, or bounded definite retries were exhausted | Correct the displayed failure cause. If the invite remains live, have the auditor explicitly request verification again after cooldown; otherwise revoke and issue a new invite. |
| Auditor is returned to invite acceptance with an ended-session message | Session expired, idle past 2 hours, revoked, network/browser change broke IP-or-UA binding, or cookie cleared | Contact the compliance team for a fresh invite. An already accepted invite cannot be reused. Verify the replacement invite to establish a new session. |
| Auditor cannot download individual evidence files | Missing view_evidence permission |
Create a new invite with view_evidence if the auditor should download files from control detail |
| Auditor cannot submit IRL items | Missing submit_irl permission |
Create a new invite with submit_irl |
| Auditor cannot see package metadata or download the package | Missing download_package permission, or no published package exists |
Check permissions; generate a package if none exists |
| Auditor cannot open the report page | Invite is missing view_report |
Create a new invite with view_report; selecting either report mutation permission includes it automatically |
| Auditor can sign off but cannot close the engagement | Invite is missing close_engagement, or sign-off has not been recorded yet |
Add close_engagement and make sure report sign-off is complete first |
| No package available for download | Package has not been generated yet | Generate one from the Evidence Package panel |
Background package job failed with too_large |
The audit cycle exceeded package artifact or bundle-size limits | Reduce package scope, split high-frequency artifacts, or remove redundant snapshots before retrying |
| Share-token download returns 429 | The token hit max_downloads or the per-IP hourly rate limit |
Revoke and issue a new token when access is still appropriate, or wait for the hourly IP window to reset |
| Share-token download returns 410 | Token is expired, revoked, malformed, for the wrong package, or otherwise invalid | Issue a fresh share token and send the endpoint separately from Authorization: Bearer <raw_token> |
| IRL requests not appearing | The auditor submitted to a different cycle | IRL requests are scoped per cycle; check the correct audit |