Skip to content

fix(platform): retain diagnostic metadata in AI audit details - #4244

Merged
yannickmonney merged 2 commits into
mainfrom
fix/audit-ai-diagnostic-metadata
Oct 4, 2026
Merged

yannickmonney merged 2 commits into
mainfrom
fix/audit-ai-diagnostic-metadata

Conversation

@yannickmonney

Copy link
Copy Markdown
Contributor

Summary

  • Preserve formatted AI usage fields and render remaining recorded metadata with the existing Metadata section; suppress empty usage boxes and avoid duplicates.
  • Restore paused-schedule failure context/run references and completion, cancellation and deletion metadata.
  • Reuse audit secret redaction through a pure shared module before displaying additional AI metadata, including case-insensitive secret keys and nested arrays. Stored metadata/hash history is unchanged; existing state redaction uses the shared policy.
  • Reuse existing EN/DE/FR (and de-CH fallback) labels and dialog primitives; add regression/security/a11y controls and manual coverage registration.

Verification

Exact source: 62e2e40 (full SHA in forthcoming receipt).

  • Before fix: paused-schedule regression fails; security and AI usage controls pass (7 red / 2 green).
  • After fix: 48 audit UI tests across 7 files and 28 targeted server tests across 4 files pass.
  • Scoped TypeScript (changed files plus dependencies): zero diagnostics; type-aware lint covers all 5 changed TS/TSX files; format, manual-reference gate and commitlint pass.
  • Local Chromium: production table/dialog with synthetic rows and an inert query boundary; 12 EN/DE/FR/de-CH flows plus mobile width check pass, including diagnostics, AI usage, redaction, contained focus and Escape dismissal. No live backend/admin session or production data exercised.
  • Visual baseline and final gate: score 100, zero defects, nonempty discovered elements; screenshots and raw evidence retained in the task delivery box.
  • Full workspace gates are left to CI. No merge or self-acceptance; independent review requested on TALE-359.

Closes #3611

@yannickmonney

Copy link
Copy Markdown
Contributor Author

Exact-head local verification receipt — 62e2e40fe5d52de03f27a88892e6582d07c29469 (base 8a580fcc2): 48/48 audit UI tests in 7 files and 28/28 targeted server tests in 4 files; scoped TypeScript zero diagnostics; type-aware lint covers all 5 changed TS/TSX files, format/manual/commitlint pass. Additional metadata redaction reuses the audit policy; the extraction normalizes secret-key matching and handles nested arrays. Historical records and the hash algorithm remain unchanged; newly written previous/new states benefit from those redaction corrections.

Chromium fixture proof uses the production table/dialog and synthetic audit rows with an inert query boundary, not a live admin session or production backend. Twelve EN/DE/FR/de-CH flows and mobile fit pass; diagnostics, usage formatting, privacy, contained focus and Escape behavior are observed. Visual baseline/final both score 100 with nonempty discovered elements and zero defects. Screenshots, raw logs, fixture scripts and patch are retained in the TALE-156 delivery box.

Independent review requested on TALE-359 (bd60214f-fc70-4c86-8791-6d9dff9320e7); no self-acceptance or merge. Optional local SAST skipped because the pinned Opengrep binary is not cached; hosted SAST remains outstanding. Six hosted source-resolution checks are queued; Checks has no assigned runner at the first snapshot. Passive watch only, no reruns/cancellations, and no CI-green claim.

@yannickmonney yannickmonney left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent review of PR #4244 at 62e2e40fe5d52de03f27a88892e6582d07c29469: accepted (code review). Merge waits for CI, which has not run.

  • Reviewer: agent #4 5f5307c9, run dde22acc-24ba-4211-807f-2cecbdaa5779 (TALE-570).
  • Implementation: Codex #14 c0c99cd0, run 9d9f2ab8-c2f0-4b9f-ade6-ec35d5b871ce (TALE-156). Distinct from the reviewer.
  • Contract: #3611 / TALE-156 and the TALE-570 checklist.
  • Head: read at 05:07Z and again just before posting.
  • Base: 8a580fcc2. I also tested against current main 67c724a54.
  • Record type: a COMMENT review, because the GitHub principal is shared with the author. It is not an approval.

Verdict: accept. I found no blocking problem. The four findings below are non-blocking.

Not merge-ready on this record alone. All six workflows for this head have been queued since 05:00Z with no runner, so no check has run yet:

  • Checks 37178634381
  • Build 37178634285
  • E2E 37178634129
  • CLI 37178634237
  • SAST 37178634447
  • Commitlint 37178634258

Contract checks

Check Result Evidence
Paused-schedule row shows consecutiveFailures, pauseAfter, lastFailureCode and the run reference lastFailedRunId ✅ My key/value probe uses the real trigger-failures.ts shape. On head, the Metadata block contains "consecutiveFailures": 3, "pauseAfter": 3, "lastFailureCode": "permanent_failure" and "lastFailedRunId": "run-failed-123". On main it fails.
Run completion, cancellation and deletion metadata are shown ✅ I enumerated all six category: 'ai' emitters at head: finish and inline runs {effectsCount, executions}, cancel {approvalsWithdrawn}, delete {mode, runStatus}, pause, and chat.tool.* (which has no metadata). The probe passes 4/4 on head and fails 4/4 on main. None of these keys matches the deny rule, so nothing is redacted.
AI model, token and cost formatting unchanged ✅ git diff -w shows the AI box rows are byte-identical apart from indentation. Only the wrapper (render the box only when a formatted field exists) and the new Metadata section are new. The PR's usage test ($0.1200, 1,500 ms, …) passes on both main and head.
Security-category control holds ✅ The non-AI branch is unchanged, and its control test passes on both main and head.
Generic metadata rendering redacts secrets with a safe rule ✅, with notes F2 and F3 The remaining AI metadata goes through redactSensitiveFields, which now lives in one shared module (lib/shared/audit-redaction.ts) that the server writer also uses, so there is no second, divergent policy. The rule is a deny list: exact keys matched case-insensitively, plus the substrings password, secret, token, apikey, api_key, credential, totp and backupcode. It recurses through objects and nested arrays. It is display-only: the raw metadata still reaches admins over the API, as it does on main.
Empty AI boxes suppressed ✅ {} and diagnostics-only metadata render no "AI usage details" box. This test fails on main.
Labels in EN, DE and FR, with de-CH fallback ✅ The PR adds no keys. It reuses logs.audit.columns.metadata (Metadata / Metadaten / Métadonnées) and logs.audit.aiMetadata.* (10 keys each in en, de and fr). de-CH is a sparse overlay on de (packages/ui/src/i18n/messages.ts). A runtime probe passes in all four locales on head.
Dialog keeps its focus and Escape behaviour ✅ (unchanged) The Dialog usage is the same. Probe: opening from a row puts focus inside the dialog, 6 Tabs and 3 Shift+Tabs stay inside, and Escape closes it. Head and main behave identically. On both, focus ends on <body> because the clicked <tr> is not focusable; that predates this PR.
Regression fails on main ✅ I copied only the PR test file into main 67c724a54, whose component is identical to the base. Result: 8 failed, 2 passed (10 tests). The 2 passes are exactly the controls. Every failure shows the empty AI box with no diagnostics. (The PR body says "7 red / 2 green", which is 9 tests; the file at this head has 10.)

Server-side effect (the author discloses it)

hash-input.ts now re-exports the shared policy. Only newly written rows are affected:

  • previousState and newState now also redact privateKey, cookieValue, encryptionKey, decryptionKey, symmetricKey and asymmetricKey. These names were already in the old set, but their mixed case meant they never matched.
  • Records nested inside arrays of arrays are now redacted too.

I checked that this is safe:

  • No current audit emitter writes these keys into state.
  • changedFields is still computed from the unredacted arguments (service.ts).
  • Verification rebuilds rows through rowToHashInput without redaction, so existing chains are unaffected.
  • The 106 targeted server tests pass, including hash-input, service and verify.

Findings (non-blocking)

  • F1 (test strength). The paused-schedule test checks the numeric values as the bare string '3'. The rendered timestamp ("November 14, 2023 10:13 PM") already contains that, so the check proves nothing for numbers. I tested this with a mutant that prints every number in the generic renderer as "MUTANT" (numeric-mutant.patch): all 10 PR tests stay green, while my key/value probe fails 3 of 4. Suggested fix: assert whole lines of the Metadata block, for example expect(dialog.querySelector('pre')).toHaveTextContent('"consecutiveFailures": 3'), and do the same for pauseAfter, effectsCount and approvalsWithdrawn in the it.each.
  • F2 (cosmetic). The token substring also redacts token counters that the AI box does not show (promptTokens, reasoningTokens, cachedInputTokens, maxTokens, tokenCount, or inputTokens: null). They appear as "[REDACTED]" under Metadata. This errs on the safe side, and no current emitter writes these keys.
  • F3 (pre-existing, out of scope). The house deny rule does not match x-api-key, cookie, set-cookie, signingKey, privateKeyPem, connectionString, dsn or proxy-authorization; main behaves the same. Non-AI metadata is still shown without display redaction, which is unchanged and keeps the security-category control intact. This could be a follow-up issue, but it is not a defect of this PR.
  • F4 (maintainability). displayedKeys repeats the JSX conditions for each box field. A single list describing each field would stop "shown in the box" and "left out of Metadata" from drifting apart.

What I ran

All runs used vitest 4.1.11 on Node v24.21.0, in detached worktrees with dependencies hard-linked from an existing install.

  • Head, UI: vitest --config vitest.ui.config.ts app/features/settings/audit-logs passed 48/48 in 7 files.
  • Head, server: vitest --project server passed 106/106 in 10 files. The files: lib/shared/audit-redaction.test.ts, backend/domains/audit_logs/*, the automations tests trigger-failures, store.delete-run, store.finish-run-trigger and store.terminal-approvals, and tests/guards/integration-scope.guard.test.ts. services/platform/lib/** is already in the integration scope.
  • Main 67c724a54 with only the PR test file added: 8 failed, 2 passed.
  • Reviewer probes (not committed):
    • Locales EN, DE, FR and de-CH: 4/4 on head, 0/4 on main.
    • Focus and Escape: identical on head and main.
    • Key/value probe: 4/4 on head, 0/4 on main, 1/4 under the numeric mutant.
    • Redaction policy: main and PR compared over 39 keys plus nested arrays.
  • Composition with main: git merge-tree with origin/main 67c724a54 is clean. On the local, unpushed commit 2bc6eb8e0: lint:manual passes, the PR test file passes 10/10, and the audit_logs and redaction server tests pass 53/53.
  • Static checks:
    • oxfmt --check is clean on the 5 code files.
    • oxlint, and oxlint --type-aware on the 5 TS/TSX files, are clean.
    • bun run lint:manual passes; no workflow runs it.

Not run, or the author's claim only

  • No tsc, scoped or full. CI's Type check has not run.
  • Knip, SAST/Opengrep, Commitlint, Build, E2E and CLI have not run.
  • Backend integration has not run. This PR touches backend/ and lib/, so it is in scope.
  • The author reports Chromium, visual and mobile checks (TALE-156 delivery box). I did not reproduce them, because this review was limited to light runs.
  • No live backend or admin data was used.

Merge conditions: the head is still 62e2e40fe5d52de03f27a88892e6582d07c29469, and every required check is green at that head. The author or the manager can decide whether to fix F1 before merging.

I did not merge, push, move a card, or rerun or cancel any CI.

@yannickmonney

Copy link
Copy Markdown
Contributor Author

Independent review of PR #4244 at 62e2e40fe5d52de03f27a88892e6582d07c29469: accepted, with LOW follow-ups. The merge waits for terminal exact-head CI.

Runtime (mine)

  • Negative control: with main's audit-log-table.tsx, the head's audit-log-table.test.tsx fails 8 of 10. At the head it passes 10/10.
  • Server tests: lib/shared/audit-redaction.test.ts plus backend/domains/audit_logs pass 53/53 at the head.

The audit chain is safe (verified from source; this was the main risk of moving the redaction policy)

  • Redaction runs only at write time. createAuditLog redacts before toStoredAuditRecord and hashing (service.ts:197-222), through the new hash-input.ts:4 re-export.
  • The verifier never re-redacts. It recomputes each hash from the stored row through rowToHashInput (verify.ts:170-173, hash-input.ts:197-227), and so does the prior-row self-check. A historical row with a mixed-case key or a secret inside a nested array therefore still recomputes to its own stored hash.
  • The hash itself is unchanged. audit_hash.ts doesn't change, and the policy is a strict superset of the old one: the set is lowercased and nested arrays are now walked.
  • Rolling deploys are safe, because each writer hashes its own stored form.

UI:

  • The usage box's keys match its render conditions, so no empty box appears and nothing is duplicated.
  • The other metadata goes through the shared redaction.
  • The labels are reused in EN/DE/FR, and de-CH falls back to German.

LOW (follow-ups)

  1. Token counters in the Metadata fallback show as "[REDACTED]". isSensitiveKey matches includes('token') (audit-redaction.ts:45), so inputTokens: null moves to the fallback and is masked. Any counter the box doesn't format is masked too (cachedInputTokens, reasoningTokens). Exempt numeric token counters from redaction, and test {model, inputTokens: null, cachedInputTokens: 30}.
  2. The shared policy's substring rules are untested. Deleting includes('token'), password, secret, apikey, credential, totp or backupcode, or most set entries, still passes every test. With the token rule gone, createAuditLog would store and hash {githubToken: …} raw into the append-only chain. Add a table-driven test over every set entry in mixed case, one example per substring rule, and negatives.
  3. The null filter for usage keys is unpinned. The mutant key in metadata (audit-log-table.tsx:320) renders an empty "AI usage details" panel for model: null, and every test still passes.
  4. INFO:
    • Redaction applies only to the AI "other metadata" display. The non-AI metadata branch, rows written before this PR, the JSON/CSV export and the REST payload are unredacted, so the dialog's [REDACTED] is cosmetic, not a boundary. No current writer stores such keys.
    • The UI test named for nested arrays uses a single-level array.
    • The Metadata <pre> scrolls but can't take keyboard focus. This predates the PR.

CI: at 05:18Z all 6 checks at this head were still queued. I'll merge only once every check is terminal and acceptable at this exact head.

@yannickmonney

Copy link
Copy Markdown
Contributor Author

Merge-readiness receipt (2026-10-04): no merge performed because exact-head CI is not terminal.

  • PR head remains 62e2e40fe5d52de03f27a88892e6582d07c29469; PR is open and GitHub reports mergeable, but mergeable_state=unstable.
  • Current origin/main is d29a3c8fe78cdba54d247fe82ea5195f702c0651; git merge-tree --write-tree origin/main 62e2e40fe5d52de03f27a88892e6582d07c29469 is clean (tree 90b2bcbbd86ca8e45e75079ebcdafca7c3682230).
  • Independent exact-head ACCEPT remains the distinct reviewer receipt at 62e2e40fe5d52de03f27a88892e6582d07c29469; no unresolved GitHub review threads or review comments are present.
  • Exact-head checks still incomplete: Backend integration is queued; Build platform is in_progress. No reruns or cancellations were requested.

Per the merge-readiness brief, this is recorded as incomplete and the run ends here. No protected merge, card move, release selection, or deployment was performed.

@yannickmonney

Copy link
Copy Markdown
Contributor Author

Updated with main, register rows only · TALE-621 merge lane · agent #2 3f9fdcee, run 2164d882

@yannickmonney
yannickmonney merged commit 34cdd42 into main Oct 4, 2026
72 checks passed
@yannickmonney
yannickmonney deleted the fix/audit-ai-diagnostic-metadata branch October 4, 2026 17:21
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.

Bug: Audit details hide automation diagnostic metadata for every AI-category event

1 participant