Skip to content

feat(admin): export audit trail as CSV and strictly validate enterprise list queries - #751

Merged
davidmckayv merged 3 commits into
CopilotKit:mainfrom
Ayush7614:feat/admin-audit-export
Oct 13, 2026
Merged

davidmckayv merged 3 commits into
CopilotKit:mainfrom
Ayush7614:feat/admin-audit-export

Conversation

@Ayush7614

Copy link
Copy Markdown
Contributor

What

Admin > Audit was a read-only screen: the only way to review the trail was to look at it. This adds the missing take-out path plus query-parity fixes in the same admin surface:

  1. Audit CSV export — GET /api/admin/audit-events?format=csv answers the same filtered query (eventType, actorUserId, initiatorKind, targetType, targetId, from, to) as RFC 4180 CSV, with an Export CSV button on the Audit page that downloads the page's current filter.
  2. Strict enterprise list queries — GET /api/admin/enterprise/models/usage?days= and GET /api/admin/enterprise/actions?limit= used Number(raw) || default, so ?days=abc, ?days=12abc, ?limit=3.9 silently returned default views while the audit/channel/people lists answer 400. Both now parse strictly (400 naming the parameter, clamp 1..365 / 1..500), matching parsePageLimit.

Why

A compliance pull, an incident timeline, or a count of what policy refused had nowhere to go — admins could only screenshot the table. The export unblocks evidence collection, and the strict parsing closes a silent-coercion inconsistency where a typo returned a plausible-looking wrong page.

What's in it

  • server/src/audit.ts: AUDIT_EXPORT_COLUMNS, auditEventsToCsv (RFC 4180 escaping, decision/bot columns, redacted payload as one JSON column), readAuditExportRows (walks matching pages via the same reader the JSON list uses; ignores list paging; caps at 5,000 rows and reports truncation).
  • server/src/app.ts: ?format=csv branch with text/csv + Content-Disposition: attachment, X-Audit-Export-Truncated header when capped, 400 on unknown format. JSON path untouched.
  • server/src/admin/routes.ts: exported pure parseEnterpriseDays / parseEnterpriseActionsLimit wired into both GETs (400s at the edge; store clamps stay as defence in depth).
  • app/src/lib/audit/queries.ts + audit.tsx: auditExportUrl(search) (filter travels, paging dropped, stray format replaced) and an Export button with loading/error states.
  • docs/architecture.md: short "Taking the trail out" subsection.
  • Tests: server/tests/audit-export.test.ts (serializer, collector incl. truncation, route: headers/body/400s/JSON-unchanged), server/tests/enterprise-list-query-validation.test.ts (defaults, clamps, 400 cases), app/tests/audit-export-url.test.ts (filter/paging/format handling).

Verification (all run locally, all green)

  • New tests: 20 pass across the 3 new files.
  • Surrounding suites: 106 pass across audit (cursor, limit, query, retention, initiator), enterprise-controls, and app audit tests.
  • bunx biome lint --error-on-warnings: clean on all 8 touched source/test files.
  • bun run --filter server typecheck and bun run --filter app typecheck: both exit 0.
  • Note: a fresh bun install was needed in this checkout before tests could resolve workspace deps (pre-existing, unrelated to this change). No dependencies added or changed by this PR.

Original work for this repo: no open issue or PR covers audit export (checked open issues/PRs before starting); branch is fresh off current upstream/main (17831b2) with nothing else on it.

…pped at the cap

Audit rows carry text people and Bots control, and a CSV cell starting with
=, +, - or @ runs as a formula in a spreadsheet, so such a cell is prefixed
with a quote and opens as text. Taking the trail out is now on the trail
itself, as audit.exported with who, how many rows and which filter. An export
that hit the 5,000-row cap set a header the page ignored, so the file read as
the whole trail; the header now carries the row count and the page says the
file holds the newest that many.
@davidmckayv
davidmckayv merged commit 6d06495 into CopilotKit:main Oct 13, 2026
19 checks passed
1123786563 pushed a commit to 1123786563/OpenBot that referenced this pull request Oct 13, 2026
合入上游 14 个提交(CopilotKit#758-CopilotKit#778):sidebar Bots/Skills 重构、Bot 页面树
(bots.$agentId.*)、legacy 路由重定向、GitHub/Slack 账号链接(CopilotKit#763)、
memory-on-Intelligence 与默认开启(CopilotKit#773/CopilotKit#776)、Web Bot Auth(CopilotKit#775)、
headed 浏览器与住宅代理(CopilotKit#772)、admin 审计 CSV 导出(CopilotKit#751)、
v0.2.0(CopilotKit#768)等。

合并要点:
- drizzle 迁移:上游 0053-0055 重编号为 0084-0086,快照按上游增量
  叠加我方 0083 合成,journal 时间戳在 0083 后递增;0076 快照缺失
  为合并前遗留,保持原样。
- createApp 尾参并入上游 identity/identityGithubCallback,两个按位
  装配的测试(self-host-banner、identity-app-wiring)改为从尾部计数。
- UI 冲突取上游重写版;我方 i18n 键补进 settings/admin locale;
  billing/openConnector/craft/workshop/marketplace 全部保留。
- agent-computer/bun.lock 重装再生(web-bot-auth 依赖)。
- 删除上游已删的 agent-dialog/inbox/bots.$agentId 与遗留损坏的
  mobile-links.test.ts。

验证:typecheck 全绿;vite build 通过;e2e 43/43(本地起服务);
迁移 journal/identity/billing 集成测试与合并前基线一致(无新增失败,
剩余失败均为合并前遗留或本机环境项:Appium 模拟器、VPN、日期腐化)。

Co-Authored-By: Claude Code <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants