Skip to content

feat(core): make emit-time redaction programmable - #466

Merged
HugoRCD merged 4 commits into
mainfrom
feat/programmable-redaction
Aug 1, 2026
Merged

feat(core): make emit-time redaction programmable#466
HugoRCD merged 4 commits into
mainfrom
feat/programmable-redaction

Conversation

@HugoRCD

@HugoRCD HugoRCD commented Aug 1, 2026

Copy link
Copy Markdown
Owner

🔗 Linked issue

Closes #463

📚 Description

Make redaction policies programmable by allowing function-valued replacement and introducing a new transform hook:

replacement as a function:

  • RedactConfig.replacement now accepts a function (matched, ctx) => string in addition to strings
  • Enables derived replacements: keep requests correlatable without exposing credentials (e.g., stable fingerprints)
  • Receives the matched value and context (path, key, groups for pattern captures)
  • Falls back to [REDACTED] if the function throws or returns non-string — a broken policy degrades to over-redaction, never leaks the raw value

New transform hook:

  • RedactConfig.transform runs before declarative stages (paths, builtins, patterns)
  • Covers policies that cannot be expressed declaratively: conditional on sibling fields, tenant-scoped, allowlist-shaped
  • Sees raw values; declarative rules still apply to whatever it leaves behind
  • Mutates the event in place (already a private clone)
  • Errors are caught and reported like drain failures — the event still logs

Build-time config safety:

  • Added hasFunctionRedactPolicy() to detect function-valued policy before JSON serialization
  • Nitro modules now warn at build time if function policy is declared in config files (which serialize to JSON), instead of silently dropping it
  • Exported RedactReplacement and RedactReplacementContext types for public API

Documentation:

  • Updated redaction guide with computed replacements and conditional policies examples
  • Clarified redaction pipeline order and when policies run relative to console output and drains

📝 Checklist

  • I have linked an issue.
  • I have updated the documentation accordingly.
  • Added comprehensive unit tests covering function replacements, transform hooks, error handling, and config serialization
  • Added integration tests verifying the feature works end-to-end with console output

https://claude.ai/code/session_01Uyng7c1TAGK6eFSBMBE33e

Summary by CodeRabbit

  • New Features

    • Added programmable redaction replacements that generate values dynamically using matched values and context.
    • Added a preprocessing transform hook that runs before declarative redaction.
    • Added safe fallback behavior when custom redaction logic fails.
    • Exported types for custom replacement callbacks and their context.
  • Bug Fixes

    • Added warnings when function-based redaction policies cannot pass through serialized runtime configuration.
  • Documentation

    • Documented custom replacements, transforms, execution order, error handling, and configuration limitations.

Redaction is the only stage that runs before the console write — enrich and
drains both run after it, so they can never scrub what already reached stdout.
That made it the one place a consumer can act in time, and its policy language
was entirely declarative: `replacement` could only be a constant.

So any policy needing logic had nowhere to run. The escape hatch was
`silent: true` plus a custom drain, which throws away the console sink to gain
a transform — not a trade you can make on a platform that ingests stdout.

`replacement` now also accepts a function, called with the matched value and
its path, so a replacement can be derived from what it replaces — a stable
fingerprint keeps requests correlatable without exposing the credential.
`transform` covers what per-value replacement cannot: policies conditional on
a sibling field, tenant-scoped, or allowlist-shaped.

Both run where redaction already ran, so ordering and the documented contract
are unchanged. Two deliberate choices:

- `transform` runs before the declarative stages, not after, so it sees raw
  values and `paths` / `builtins` / `patterns` still apply to what it leaves
  behind. A hook that misses a field is not the last line of defence.
- A replacement that throws falls back to `[REDACTED]` rather than emitting
  the raw value. Degrading to over-redaction is the only safe direction for
  a stage whose job is to not leak.

Function policy cannot cross the build-time config bridges, which serialize to
JSON. Rather than drop it silently — the failure mode of #408 and #441 — the
Nitro modules warn, and the docs point to declaring it at runtime.

Closes #463

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uyng7c1TAGK6eFSBMBE33e
@vercel

vercel Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
evlog-docs Ready Ready Preview, v0 Aug 1, 2026 11:21am
evlog-render-lab Ready Ready Preview Aug 1, 2026 11:21am
evlog-telemetry Ready Ready Preview Aug 1, 2026 11:21am
just-use-evlog Ready Ready Preview Aug 1, 2026 11:21am

Request Review

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Thank you for following the naming conventions! 🙏

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@HugoRCD, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 30 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 00a6cd0f-e420-4422-9738-2135b5ecbf61

📥 Commits

Reviewing files that changed from the base of the PR and between d7c458a and 7839599.

📒 Files selected for processing (1)
  • .changeset/programmable-redaction.md
📝 Walkthrough

Walkthrough

Changes

Programmable emit-time redaction now supports computed replacement callbacks and synchronous transforms. The pipeline passes match context, handles callback failures, preserves functions during normalization, warns about JSON configuration loss, and documents the new behavior. The lab also receives minor syntax and promise-return cleanup.

Programmable redaction

Layer / File(s) Summary
Redaction callback contracts
packages/evlog/src/types.ts, packages/evlog/src/index.ts
Adds replacement context, function-valued replacement, and the synchronous transform hook. Exports the new types.
Emit-time redaction pipeline
packages/evlog/src/redact.ts, packages/evlog/test/core/redact.test.ts, packages/evlog/test/core/redact-integration.test.ts
Runs transforms before declarative redaction. Supports dynamic path and pattern replacements with paths, keys, and capture groups. Logs transform failures and uses [REDACTED] for invalid replacement results.
Configuration bridge handling
packages/evlog/src/redact.ts, packages/evlog/src/nitro/*.ts, packages/evlog/src/nitro-v3/module.ts, packages/evlog/test/core/redact.test.ts
Preserves function policies during normalization. Detects policies before JSON serialization and warns from both Nitro module variants.
Documentation and release notes
apps/docs/content/2.learn/6.redaction.md, apps/docs/content/7.reference/1.configuration.md, .changeset/programmable-redaction.md
Documents replacement callbacks, transform ordering, error handling, mutation behavior, and runtime configuration restrictions.

Lab cleanup

Layer / File(s) Summary
Lab syntax and promise cleanup
apps/lab/app/pages/index.vue, apps/lab/app/utils/lab/assets.ts, apps/lab/app/utils/lab/db.ts
Uses Vue shorthand binding and removes unnecessary async declarations while preserving existing promise return types and behavior.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant EventEmitter
  participant redactEvent
  participant ConsoleSink
  EventEmitter->>redactEvent: finalize event and apply transform
  redactEvent->>redactEvent: apply path and pattern replacements
  redactEvent-->>EventEmitter: return redacted event
  EventEmitter->>ConsoleSink: serialize and write event
Loading

Possibly related PRs

  • HugoRCD/evlog#365: Extends the same redaction pipeline and preserves its non-mutating behavior.
  • HugoRCD/evlog#371: Overlaps in path-based redaction helpers and redaction tests.
  • HugoRCD/evlog#376: Overlaps in redaction configuration resolution and event redaction behavior.

Suggested labels: documentation

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The changes to apps/lab are unrelated to programmable redaction and issue #463. Remove the unrelated apps/lab changes or explain why they are required for the programmable redaction objective.
Docstring Coverage ⚠️ Warning Docstring coverage is 61.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: programmable emit-time redaction.
Description check ✅ Passed The description follows the template, links issue #463, explains the implementation, and confirms documentation and test coverage.
Linked Issues check ✅ Passed The implementation addresses issue #463 with programmable replacements and transforms before console output, safe error handling, tests, and documentation.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/programmable-redaction

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@HugoRCD HugoRCD self-assigned this Aug 1, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.changeset/programmable-redaction.md:
- Line 20: Update the wording in programmable-redaction.md to distinguish
replacement-function failures from transform failures: state that a throwing
replacement function falls back to “[REDACTED]”, while a throwing transform is
caught and skipped without replacing the event.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9bc7de58-803a-49d6-b4b9-05e57ba79691

📥 Commits

Reviewing files that changed from the base of the PR and between 4752ccf and 5d99391.

📒 Files selected for processing (10)
  • .changeset/programmable-redaction.md
  • apps/docs/content/2.learn/6.redaction.md
  • apps/docs/content/7.reference/1.configuration.md
  • packages/evlog/src/index.ts
  • packages/evlog/src/nitro-v3/module.ts
  • packages/evlog/src/nitro/module.ts
  • packages/evlog/src/redact.ts
  • packages/evlog/src/types.ts
  • packages/evlog/test/core/redact-integration.test.ts
  • packages/evlog/test/core/redact.test.ts

Comment thread .changeset/programmable-redaction.md Outdated
Not related to the redaction change in this branch, but they fail `lint` and
`autofix` on every PR until main is clean, so they are fixed here rather than
left to mask a real regression on the next push.

- index.vue: `:suggested-name` same-name shorthand, matching the sibling props
  on the same element. Compiles to the identical `_ctx.suggestedName` binding.
- assets.ts, db.ts: both functions were `async` with a single `return <promise>`
  body. The declared `Promise<T>` return type is unchanged and neither callee
  can throw synchronously — `request` is itself async — so dropping `async`
  preserves behaviour.

Also narrow the changeset wording: it read as though any throwing function fell
back to `[REDACTED]`, which is true of `replacement` but not of `transform`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uyng7c1TAGK6eFSBMBE33e
@pkg-pr-new

pkg-pr-new Bot commented Aug 1, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/@evlog/cli@466
npm i https://pkg.pr.new/evlog@466
npm i https://pkg.pr.new/@evlog/nuxthub@466
npm i https://pkg.pr.new/@evlog/telemetry@466

commit: 7839599

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.changeset/programmable-redaction.md:
- Line 20: Update the changeset description to document that resolveReplacement
falls back to [REDACTED] when a replacement callback returns a non-string value,
alongside the existing throwing-callback fallback behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e07b95ac-2a49-4900-a158-22d50582f2ae

📥 Commits

Reviewing files that changed from the base of the PR and between 5d99391 and d7c458a.

📒 Files selected for processing (4)
  • .changeset/programmable-redaction.md
  • apps/lab/app/pages/index.vue
  • apps/lab/app/utils/lab/assets.ts
  • apps/lab/app/utils/lab/db.ts

Comment thread .changeset/programmable-redaction.md Outdated
`resolveReplacement` treats a callback returning a non-string the same as one
that throws — both fall back to `[REDACTED]`. The prose docs already said so;
the changeset mentioned only the throwing case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uyng7c1TAGK6eFSBMBE33e
…action

# Conflicts:
#	apps/lab/app/utils/lab/assets.ts
#	apps/lab/app/utils/lab/db.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

redact cannot express a computed replacement, so anything needing logic must be scrubbed before emit()

2 participants