Skip to content

feat(audit): Operate · Audit log page (Batch 4 / T402, PR-13) - #32

Merged
ghbvf merged 4 commits into
developfrom
012-b4-audit
Jun 2, 2026
Merged

ghbvf merged 4 commits into
developfrom
012-b4-audit

Conversation

@ghbvf

@ghbvf ghbvf commented Jun 2, 2026

Copy link
Copy Markdown
Owner

Summary

Batch 4 / T402 — the Operate · Audit log page, first of the two PRs for #15 (audit cell; config+flags follow in PR-14, stacked on this branch).

New cell @gocell/audit mirroring the @gocell/access structure: a tamper-evident audit trail with day-grouped entries, actor pills, quick filters, and a hash-chain integrity panel.

  • api/audit.ts — listAudit() against /api/v1/audit/, cursor-paginated. Row type AuditEntry is codegen-bound to HttpAuditListV1Response['data'][number] (no hand-written DTO). Local ListAuditParams documented as provisional pending a backend request schema.
  • stores/useAuditStore.ts (audit.query) — list/loadMore, client-side three-dimensional filter (free-text + actor-kind + action-namespace), entriesByDay grouping, chainStatus getter.
  • lib/hashChain.ts — pure verifyChain() (ok/broken/unavailable), fully tested, ready to activate when the backend exposes hash fields.
  • lib/auditClassify.ts — classifyActor() (prefix heuristic), groupByDay().
  • views/AuditView.vue + components/ActorPill.vue — V1-Linear chrome, tokens-only styling, semantic markup.

Backend gaps (frontend-first per the #15 directive — graceful degradation, no fabricated data)

The contract delivers fewer fields than the design (dev-audit.jsx). Handled by degrading, not faking:

Missing in contract Frontend behaviour BR
hash / prevHash Chain panel shows explicit "verification unavailable" (role=status), never a fake OK. Algorithm is implemented + tested, activates when fields ship. BR-006
actorType classifyActor() prefix heuristic; the raw actorId is always the visible label, kind only drives the decorative dot. BR-006
result / reason / actor.ip / actor.mfa Not displayed; result-based quick filters omitted (only actor-kind + action-namespace presets kept). BR-006
audit-list query schema Local ListAuditParams; client-side filter over the loaded page. BR-006

BR-006 (audit hash-chain + actor/result fields) will be filed against ghbvf/gocell.

Packages touched

  • @gocell/audit (new cell): api / store / lib / view / component / README / exports.
  • @gocell/core: i18n audit.log.* block (zh-CN + en-US) — established pattern (access keys live here too).
  • apps/web: /audit route (PDP read:audit, fail-closed) + @gocell/audit dependency.
  • packages/contracts/: not modified — consumed read-only.

Test plan

  • pnpm -F @gocell/audit test --run — 70 tests pass
  • pnpm -F @gocell/audit test:coverage — 94.66% lines / 82.81% branches / 86.2% functions (≥80%)
  • pnpm -w typecheck — clean (incl. en-US satisfies MessageSchema)
  • pnpm -w lint — 0 issues
  • pnpm -w test --run — 49 files / 692 tests pass (incl. new /audit router assertion)

Refs #15

ghbvf and others added 3 commits June 2, 2026 09:13
…r + chain stub

Implements @gocell/audit Batch 4 / T402:
- package.json: deps, exports (./stores + ./views/audit), test scripts
- vitest.config.ts + tsconfig.json: mirrors access cell setup
- src/api/audit.ts: listAudit() + AuditEntry type (codegen-bound via HttpAuditListV1Response)
- src/lib/hashChain.ts: pure verifyChain() — ok/broken/unavailable paths; ready for BR-006
- src/lib/auditClassify.ts: classifyActor() heuristic + groupByDay() + formatDayLabel()
- src/stores/useAuditStore.ts: fetchList/loadMore/filteredEntries/entriesByDay/chainStatus
- src/components/ActorPill.vue: aria-hidden dot + actorId label, variant via classifyActor
- src/views/AuditView.vue: day-grouped master-detail, filter toolbar, quick-filter chips
- 70 tests, all green; coverage statements/lines/branches/functions all ≥ 80%

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- apps/web: register /audit (PDP read:audit, fail-closed) as AppShellLayout
  child; lazy-load @gocell/audit/views/audit subpath; add @gocell/audit dep.
- core i18n: add audit.log.* block (zh-CN + en-US) for the Audit log page.
- router spec: assert /audit resolves nested with auth + PDP meta.

Refs #15

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…th isRecord guard

- api/audit.ts: remove AuditListPage interface (a hand-written mirror of the
  HttpAuditListV1Response contract envelope); listAudit now returns the contract
  type directly. Enforces "契约类型必须来自 @gocell/contracts;不在业务包里手写后端 DTO".
- stores/useAuditStore.ts: replace `as Record<string, unknown>` casts in chainStatus
  with an isRecord() type guard, honoring the existing "no `as` cast" comment and
  the unknown + 类型守卫 rule.

Behavior unchanged; 74 audit tests pass, typecheck + lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ghbvf

ghbvf commented Jun 2, 2026

Copy link
Copy Markdown
Owner Author

六维度 Review(包边界 / Vue 模板 / TS / 包架构 / a11y / 性能 / 设计一致性)

整体质量积极:包边界完全合规(@gocell/audit 无横向业务 cell import,Can 来自 @gocell/core,HTTP 经 @gocell/request),BR-006 hash chain 降级状态诚实标注,74 条单测覆盖 store/api/lib/view,i18n 双语齐备,prefers-reduced-motion 已兜底。

已修复(commit c5df867)

  1. [TS / 包架构] api/audit.ts — 删除手写的 AuditListPage DTO,它逐字段镜像了 HttpAuditListV1Response 契约信封,违反「契约类型必须来自 @gocell/contracts;不在业务包里手写后端 DTO」。listAudit 改为直接返回契约类型。
  2. [TS] stores/useAuditStore.ts — chainStatus 中 as Record<string, unknown> 强转与其自身注释「no `as` cast」矛盾;引入 isRecord() 类型守卫消除强转,符合「unknown + 类型守卫」规范。

行为不变,typecheck / lint / 74 测试全绿。

评估后不改(登记说明)

  • CSS 字号 / padding 字面量(26px / 12.5px / 28px 32px 等):tokens.css 当前只暴露 color / radius 变量,未暴露 spacing / font-size token。no-css-magic-number lint 仅拦截 color/radius(CI 已通过),这些 px 值是全仓既有模式,非本 PR 引入。属 token 系统级缺口(Cx3),不在本 slice churn。
  • 快速过滤 chip 无 aria-pressed:chip 语义是「应用预设」(applyQuickFilter 先 reset 再 set),非 toggle 按钮,aria-pressed 不适用;点击后 filter 下拉 + count 实时反馈状态。
  • aria-current="'true''" 字符串 vs 布尔:现写法已用 ? 'true' : undefined,未选中行不输出 aria-current="false" 噪声,输出与布尔等价,无需改。
  • export / verify 按钮未接 handler:与同文件 time-range segment 一样属「UI-only 脚手架」(注释已注明),留待后续 export/verify slice 接线,与页面「explicit unavailable」哲学一致。
  • hit area(chip 26px / seg-btn 30px):与设计系统紧凑密度一致(PRD §4 允许 30px)。

结论:✅ 可合并。

@ghbvf
ghbvf merged commit 387dd84 into develop Jun 2, 2026
3 checks passed
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.

1 participant