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_user match).
    • Org-grant installs via SpiceDB LookupResources(org, read) — so delegates, org admins, and auditors see events on orgs they didn't install themselves.

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 filterall / 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:

  1. Actor + target. "Alice granted Bob maintainer on acme."
  2. Metadata. The role + source confirm the action. source = "grant" rules out the installer's auto-grant (see Org membership).
  3. 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.synced row.