43 lines
2.1 KiB
Markdown
43 lines
2.1 KiB
Markdown
|
|
---
|
||
|
|
kind: task
|
||
|
|
slug: audit-instrument-rate-limit-trips
|
||
|
|
title: Write audit-log rows on every rate-limit trip
|
||
|
|
plan_slug: multitenant-saas-hardening
|
||
|
|
epic_slug: tenant-lifecycle
|
||
|
|
status: draft
|
||
|
|
priority: P2
|
||
|
|
tenant_id: global
|
||
|
|
owner: unassigned
|
||
|
|
cursor_todo_id: null
|
||
|
|
updated_at: "2026-06-02"
|
||
|
|
---
|
||
|
|
|
||
|
|
# Task summary
|
||
|
|
|
||
|
|
Rate-limit trips currently only go to stdout (`console.warn`). The parent task spec called for an `audit_log` row on every trip so we can:
|
||
|
|
|
||
|
|
1. See in the audit view whether anyone's actually hitting the limits in practice.
|
||
|
|
2. Build abuse-signal dashboards on top of `audit_log` instead of scraping logs.
|
||
|
|
|
||
|
|
This is a small follow-up because we kept `Task-rate-limit-and-abuse-guardrails` scoped to "the wire-ins ship cleanly" and didn't want to churn `auth.ts` and `invites.ts` a second time inside the same task.
|
||
|
|
|
||
|
|
## Subtasks
|
||
|
|
|
||
|
|
- [ ] At every `if (!rl.allowed) { ... }` site, call `recordAudit(...)` before the rejection. For the Credentials path, the workspace context isn't known (the user isn't even authenticated yet), so we need a different write path:
|
||
|
|
- Option A: skip audit for sign-in trips and only log to stdout (current behavior).
|
||
|
|
- Option B: add a `system_audit_log` table for non-workspace events (sign-in attempts, rate-limit trips on unauthenticated routes).
|
||
|
|
- Option C: stamp `workspace_id = '00000000-0000-0000-0000-000000000000'` as a sentinel "system" workspace.
|
||
|
|
- Recommended: **A for v1** (no schema change), revisit when we have a real need for cross-workspace audit. For workspace-scoped trips (invite-create), use the regular `recordAudit()` flow.
|
||
|
|
- [ ] Wire `recordAudit` into the `invites.create` rate-limit branch. Action: `rate_limit.tripped`. Metadata: `{ key, retry_after_ms, limit, window_ms }`.
|
||
|
|
- [ ] Add the `rate_limit.tripped` action to the audit page's `ACTION_LABELS` map.
|
||
|
|
|
||
|
|
## Acceptance criteria
|
||
|
|
|
||
|
|
- [ ] Triggering the `invites.create` rate-limit produces a `rate_limit.tripped` row in the audit view.
|
||
|
|
- [ ] Sign-in trips remain logged to stdout (decision A above) until a `system_audit_log` table is justified.
|
||
|
|
|
||
|
|
## Links
|
||
|
|
|
||
|
|
- Parent: `./Task-rate-limit-and-abuse-guardrails.md`
|
||
|
|
- Epic: `./Epic-tenant-lifecycle.md`
|