Skip to content

feat(projections): preserve definition write provenance#424

Merged
yordis merged 1 commit into
masterfrom
yordis/feat-projection-definition-metadata
Jul 7, 2026
Merged

feat(projections): preserve definition write provenance#424
yordis merged 1 commit into
masterfrom
yordis/feat-projection-definition-metadata

Conversation

@yordis

@yordis yordis commented Jul 6, 2026

Copy link
Copy Markdown
Member
  • Projection definition writes need caller-supplied provenance so administrative clients can distinguish user-authored definitions from tool-authored updates.
  • The embedded UI should leave the same audit trail as other management clients when it changes projection definitions.
  • Projection metadata must survive the management pipeline without being mistaken for custom event metadata in tests.

@cursor

cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches projection management writes and gRPC contract (proto.lock); behavior is additive but changes how definition events are stored on the projections stream.

Overview
Adds optional caller annotations on projection Create and Update (gRPC annotations map and internal definitionMetadata bytes) so definition changes can record who initiated them.

ManagedProjection now attaches that payload to ProjectionUpdated stream writes when metadata is present, using the property-metadata event encoding instead of leaving the metadata slot empty. Pending metadata is versioned and cleared after a successful write, on config-only updates, and on fault.

The embedded UI supplies JSON annotations (trogondb.com/tool, version, operation) on create/update. Tests assert the bytes are persisted with IsPropertyMetadata, and the core test helper propagates the flag and reads CustomMetadata where appropriate.

Reviewed by Cursor Bugbot for commit f2dfe87. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 58 seconds

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: CHILL

Plan: Pro

Run ID: eb33b3ec-f418-4d4d-a752-8a32af998897

📥 Commits

Reviewing files that changed from the base of the PR and between 635cb6c and f2dfe87.

⛔ Files ignored due to path filters (1)
  • proto.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • src/EventStore.ClusterNode/Components/Services/ProjectionBrowserService.cs
  • src/EventStore.Core.Tests/Helpers/TestFixtureWithExistingEvents.cs
  • src/EventStore.Projections.Core.Tests/Services/projections_manager/when_posting_a_persistent_projection_and_writes_succeed.cs
  • src/EventStore.Projections.Core.Tests/Services/projections_manager/when_updating_a_persistent_projection_query_text.cs
  • src/EventStore.Projections.Core/Messages/ProjectionManagementMessage.cs
  • src/EventStore.Projections.Core/Services/Grpc/ProjectionManagement.Create.cs
  • src/EventStore.Projections.Core/Services/Grpc/ProjectionManagement.Update.cs
  • src/EventStore.Projections.Core/Services/Grpc/ProjectionManagement.cs
  • src/EventStore.Projections.Core/Services/Management/ManagedProjection.cs
  • src/EventStore.Projections.Core/Services/Management/ProjectionManager.cs
  • src/Protos/Grpc/projections.proto

Walkthrough

This PR introduces "definition metadata" for projections, carrying tool/operation/property information from the embedded UI and gRPC clients through command messages, ProjectionManager, and ManagedProjection persistence. The proto schema adds properties maps to Create/Update options. A separate unrelated fixture fix updates parked-message metadata parsing and PrepareFlags handling.

Changes

Projection Definition Metadata

Layer / File(s) Summary
Proto schema
src/Protos/Grpc/projections.proto
Adds properties map fields to CreateReq.Options and UpdateReq.Options.
gRPC metadata conversion
src/EventStore.Projections.Core/Services/Grpc/ProjectionManagement*.cs
Adds ToMetadata helper converting protobuf properties to bytes; Create/Update handlers pass converted metadata into published commands.
Embedded UI metadata generation
src/EventStore.ClusterNode/Components/Services/ProjectionBrowserService.cs
Adds DefinitionMetadataFor helper building a Struct with tool/tool_version/operation, used in create/update commands.
Command message contracts
src/EventStore.Projections.Core/Messages/ProjectionManagementMessage.cs
ProjectionPost, Post, and UpdateQuery gain DefinitionMetadata fields/properties and constructor parameters.
ProjectionManager propagation
src/EventStore.Projections.Core/Services/Management/ProjectionManager.cs
NewProjectionInitializer and PendingProjection carry and forward DefinitionMetadata.
ManagedProjection persistence
src/EventStore.Projections.Core/Services/Management/ManagedProjection.cs
Tracks _pendingDefinitionMetadata across update/fault/write lifecycle and writes it into the persisted event.
Tests
src/EventStore.Projections.Core.Tests/Services/projections_manager/*
New tests assert written events carry property metadata matching the passed definition metadata.

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

Test Fixture Parked-Message Fix

Layer / File(s) Summary
ProcessWrite/EventCommitted updates
src/EventStore.Core.Tests/Helpers/TestFixtureWithExistingEvents.cs
PrepareFlags.IsPropertyMetadata now conditionally set; parked metadata parsed from CustomMetadata instead of Metadata.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ProjectionManagementCreate as ProjectionManagement.Create
  participant Publisher
  participant ProjectionManager
  participant ManagedProjection

  Client->>ProjectionManagementCreate: CreateReq(options.Properties)
  ProjectionManagementCreate->>ProjectionManagementCreate: ToMetadata(options.Properties)
  ProjectionManagementCreate->>Publisher: Post(..., definitionMetadata)
  Publisher->>ProjectionManager: NewProjectionInitializer(definitionMetadata)
  ProjectionManager->>ManagedProjection: InitializeNew(definitionMetadata)
  ManagedProjection->>ManagedProjection: TryCreatePersistedStateEvent(_pendingDefinitionMetadata)
Loading

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

Poem

A rabbit hops through metadata trails,
Tool and version tucked in tinyails,
Properties map to bytes so neat,
Persisted safe, the update's complete,
Hop hop hooray, no fields left stale! 🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly captures the main change: preserving projection definition write provenance.
Description check ✅ Passed The description is on-topic and matches the projection provenance and metadata changes in the patch.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch yordis/feat-projection-definition-metadata

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/EventStore.Projections.Core/Services/Management/ManagedProjection.cs (1)

936-977: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Don't clear _pendingDefinitionMetadata unconditionally on success. UpdateQuery and InitializeNew reuse this shared staging field, so an older completion can erase a newer pending value before the next persisted-state event is built. Clear only the metadata snapshot that belongs to the completed write.

🤖 Prompt for 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.

In `@src/EventStore.Projections.Core/Services/Management/ManagedProjection.cs`
around lines 936 - 977, The success path in WritePersistedStateCompleted is
clearing the shared _pendingDefinitionMetadata field unconditionally, which can
erase newer staged metadata from UpdateQuery or InitializeNew before it is used.
Update the completion handling so it only clears the metadata snapshot
associated with the completed write, and avoid resetting the shared staging
field unless it still matches the write being finalized.
🧹 Nitpick comments (1)
src/EventStore.Projections.Core/Services/Management/ProjectionManager.cs (1)

1487-1527: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor: no default value for definitionMetadata.

Unlike ProjectionPost/Post/UpdateQuery (all default definitionMetadata to null), this constructor requires it explicitly. Since NewProjectionInitializer and PendingProjection are public nested types, adding a default (byte[] definitionMetadata = null) would keep the API consistent with the rest of the stack and avoid breaking any external callers of this constructor.

🤖 Prompt for 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.

In `@src/EventStore.Projections.Core/Services/Management/ProjectionManager.cs`
around lines 1487 - 1527, The NewProjectionInitializer constructor currently
requires definitionMetadata explicitly, unlike the related ProjectionPost, Post,
and UpdateQuery APIs that default it to null. Update the
NewProjectionInitializer signature in ProjectionManager to make
definitionMetadata optional with a null default, and keep the existing fallback
to Empty.ByteArray in the constructor body so external callers of this public
nested type remain compatible.
🤖 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.

Outside diff comments:
In `@src/EventStore.Projections.Core/Services/Management/ManagedProjection.cs`:
- Around line 936-977: The success path in WritePersistedStateCompleted is
clearing the shared _pendingDefinitionMetadata field unconditionally, which can
erase newer staged metadata from UpdateQuery or InitializeNew before it is used.
Update the completion handling so it only clears the metadata snapshot
associated with the completed write, and avoid resetting the shared staging
field unless it still matches the write being finalized.

---

Nitpick comments:
In `@src/EventStore.Projections.Core/Services/Management/ProjectionManager.cs`:
- Around line 1487-1527: The NewProjectionInitializer constructor currently
requires definitionMetadata explicitly, unlike the related ProjectionPost, Post,
and UpdateQuery APIs that default it to null. Update the
NewProjectionInitializer signature in ProjectionManager to make
definitionMetadata optional with a null default, and keep the existing fallback
to Empty.ByteArray in the constructor body so external callers of this public
nested type remain compatible.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 399fd764-7afc-4cf4-b4c8-af873bbbb0aa

📥 Commits

Reviewing files that changed from the base of the PR and between fefe464 and 635cb6c.

⛔ Files ignored due to path filters (1)
  • proto.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • src/EventStore.ClusterNode/Components/Services/ProjectionBrowserService.cs
  • src/EventStore.Core.Tests/Helpers/TestFixtureWithExistingEvents.cs
  • src/EventStore.Projections.Core.Tests/Services/projections_manager/when_posting_a_persistent_projection_and_writes_succeed.cs
  • src/EventStore.Projections.Core.Tests/Services/projections_manager/when_updating_a_persistent_projection_query_text.cs
  • src/EventStore.Projections.Core/Messages/ProjectionManagementMessage.cs
  • src/EventStore.Projections.Core/Services/Grpc/ProjectionManagement.Create.cs
  • src/EventStore.Projections.Core/Services/Grpc/ProjectionManagement.Update.cs
  • src/EventStore.Projections.Core/Services/Grpc/ProjectionManagement.cs
  • src/EventStore.Projections.Core/Services/Management/ManagedProjection.cs
  • src/EventStore.Projections.Core/Services/Management/ProjectionManager.cs
  • src/Protos/Grpc/projections.proto

@yordis yordis force-pushed the yordis/feat-projection-definition-metadata branch from 635cb6c to 4a77d53 Compare July 7, 2026 00:45
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
@yordis yordis force-pushed the yordis/feat-projection-definition-metadata branch from 4a77d53 to f2dfe87 Compare July 7, 2026 00:47
@yordis yordis merged commit 8141ce5 into master Jul 7, 2026
21 checks passed
@yordis yordis deleted the yordis/feat-projection-definition-metadata branch July 7, 2026 01:23
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