Documentation › Sponsor guides
Audit log + compliance
Steward records every security-relevant action to an append-only audit log. The sponsor persona uses this for three things: routine "who did what on our org" review, compliance evidence (Service Organization Control 2 (SOC2) track, regulated industries), and incident forensics. This doc explains what's recorded, where to look, and how to export.
What's recorded
Every audit row carries:
| Field | Meaning |
|---|---|
| Actor | The user who performed the action, by app.users.id and GitHub login. |
| Impersonator | Set when a Steward staff member acted-as the user (rare; recorded for dual-trail). |
| Event type | A taxonomy string like org.member_granted or install.synced. |
| Target | Kind + id of what the action targeted (e.g. repo + UUID). |
| Metadata | A JSON blob with action-specific detail (the role granted, prior status, reason). |
| IP + User-Agent | Captured from the originating request. Trusted-proxy aware. |
is_bot |
Pre-computed at write time so the "Show: Bots" filter doesn't need to re-classify. |
created_at |
UTC timestamp. |
The taxonomy is a closed set of ~40 event-type constants in
internal/api/web/internal/audit/audit.go. New types are
added by code change, not configuration — every audit row maps
to a known event that operators can reason about.
Major event families
The taxonomy groups roughly into:
| Family | Examples | What it covers |
|---|---|---|
| User identity | user.signed_in, user.signed_in_oidc, user.account_deleted, user.profile_updated |
OAuth + OIDC sign-ins, account lifecycle, profile changes. |
| Install lifecycle | install.synced, install.deleted, install.suspended, install.self_healed, install.user_resynced |
GitHub App installation events — who installed where, when. |
| Org membership | org.member_granted, org.member_revoked, org.member_skipped |
Role grants/revokes covered in Org membership. |
| CLA | cla.corporate_signed, cla.corporate_employee_added, cla.gate_passed, cla.signatures_exported |
Per-PR CLA-gate passes + corporate-CLA mutations. |
| Evaluation | evaluation.created |
Records who triggered each evaluation enqueue. |
| Billing | subscription.updated, subscription.canceled, sponsorship.comp_granted, sponsorship.refund_initiated |
Subscription state changes; staff-side comp grants. |
| API tokens | api_token.minted, api_token.revoked, api_token.used |
External-integrator persona. The used event is dedup'd to first-call-per-hour to keep the log readable. |
| SSO (Business) | org.sso_configured, org.sso_enforce_toggled, user.jit_provisioned |
OIDC provider config, enforce-required toggles, first-sighting of a JIT-provisioned identity. |
| Mentorship | mentorship.transferred |
Mentor → mentor hand-offs (the lineage view source). |
| Staff | impersonation.started, impersonation.ended, staff.relation_granted, billing.exempt_granted |
Steward-staff actions — visible to you when they touched your org. |
Append-only + fail-open
Two properties worth knowing:
- Append-only. Rows are written but never updated or deleted. A correction emits a new event (e.g. a revoke following a grant); the original row stays.
- Fail-open. If the audit insert itself fails (DB hiccup, full disk), the action is not rolled back — auditing the action and performing the action are independent concerns. The failure is logged at warn level. This is the right trade-off when the alternative is "the user can't sign in because the audit table is unhappy." Audit reliability comes from the DB's own durability, not from blocking the action on it.
If you need true synchronous audit guarantees (regulated industries), Business-tier customers can request the "audit-blocking mode" toggle. By default it's off.
Where to look
Two surfaces for the sponsor persona:
/sponsor/audit — the compliance feed
The primary view. Two-scope toggle at the top:
actor— events the caller emitted (their own history). Same query that backs/app/account.visible(default) — events on resources the caller has read access to:- Installer-scoped installs (
installed_by_usermatch). - Org-grant installs via SpiceDB
LookupResources(org, read)— so delegates, org admins, and auditors see events on orgs they didn't install themselves.
- Installer-scoped installs (
The default visible scope is the right starting point for
the sponsor persona. Switch to actor when you specifically
want "what have I done lately."
Other controls:
- Actor-type filter —
all/humans/bots. Client- side filter on the current page; useful for hiding the noise from automated bot actions when reviewing human-driven changes. - Load more — cursor-paginated, 100 rows per page. Successive pages stitch together for the CSV export.
/app/audit — the per-user view
Same shape, but always scoped to actor. The right page for
"audit my own actions" workflows.
Reading a row
Click an event row to expand the JSON metadata. Worked example
for org.member_granted:
event_type: org.member_granted
actor: alice (alice@example.com)
target: org / acme
metadata:
role: "maintainer"
granted_to: "bob"
source: "grant"
granted_at: "2026-06-09T14:23:11Z"
ip: 203.0.113.42
user_agent: Mozilla/5.0 ...
created_at: 2026-06-09T14:23:11.842Z
Three things to look at:
- Actor + target. "Alice granted Bob
maintaineron acme." - Metadata. The role + source confirm the action.
source = "grant"rules out the installer's auto-grant (see Org membership). - IP + User-Agent. Useful for incident response — was this a known IP, a known browser?
For impersonation rows, the actor is the impersonated user (Bob), while the impersonator field captures the staff member (Eve). Both identities surface so the dual-trail is trivially auditable.
Exporting for compliance
Two export shapes:
CSV on the page (every tier)
The CSV button at the top of /sponsor/audit downloads the
current page (up to 100 rows) in the current scope as
CSV. For longer windows, click Load more to stitch
additional pages into the page set before exporting.
This is the right shape for ad-hoc evidence requests — "send me the last week of role grants on our prod-corpus org."
Retention bundle (Business + Auditor persona)
/cc/audit (staff-only) and AuditService.ExportRetentionBundle
produce a retention bundle: a single ZIP containing every
audit event in a date range, per-section row counts for
quick verification, and the export's own
audit.bundle_exported row so you can prove later that the
bundle came from Steward.
The bundle caps at 50,000 events per export. If your window
exceeds it, the truncated flag is set and a subsequent
export from the truncation timestamp picks up the rest. The
60-day SOC2-evidence window typically fits in one bundle for
all but the largest fleets.
The retention bundle is the right shape for SOC2 audit walkthroughs and annual compliance reviews. The CSV covers everything else.
Tier gating
| Tier | Audit features |
|---|---|
| Community | /app/audit for personal actions only. No /sponsor/audit. |
| Pro | Same as Community. Single-seat tier — no team-level audit need. |
| Team | Basic /sponsor/audit with the actor / visible scope toggle and CSV export. Sufficient for "who did what" review without compliance evidence requirements. |
| Business | Full surface — /sponsor/audit + the retention bundle export + audit-blocking mode toggle (off by default). SOC2-track features unlock here. |
Upgrades don't backfill — audit events from before the upgrade are visible only at the tier they were recorded under. Events from before any subscription are visible at Community level.
What's next
- Org membership — the role
grants/revokes that show up in
org.member_*events. - Sponsor-a-repo (forthcoming in slice 5) — funding-as-buyer model + the audit events it emits.
- Set up your sponsorship —
the first-time setup that produces the initial
install.syncedrow.
Steward