Skip to content

feat(workflowbuilder): broken workflow refs and cross-module delete guards#122

Merged
phuongnse merged 9 commits into
mainfrom
cursor/workflowbuilder-broken-refs-23c6
May 25, 2026
Merged

feat(workflowbuilder): broken workflow refs and cross-module delete guards#122
phuongnse merged 9 commits into
mainfrom
cursor/workflowbuilder-broken-refs-23c6

Conversation

@phuongnse

@phuongnse phuongnse commented May 25, 2026

Copy link
Copy Markdown
Owner

Summary

Workflow reference read models, sync on workflow mutations, publish gating via WorkflowReferenceSyncResult, gRPC deletion guard, Kafka broken-ref handlers, and supporting CI/docs.

Review fixes: improved — single UoW publish path; generic agent checklist for review feedback (best-practice self-check, Gate 3 count).

Linked spec

  • E04 workflow builder — broken references / publish guard
  • E05 form builder — delete guard via gRPC

Requirements

  • Gate 0 — AC map
  • Gate 1 — build / test / format
  • Gate 2 — epic docs + doc drift
  • Gate 3 — retrospective (incl. review-shortcut question)
Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Form deletion guard via cross-service checks; FormDeleted event/topic and Avro schema added.
    • Workflow reference read models track form/model health; steps/triggers expose an isBroken state.
    • Publish now validates references and blocks publishing when broken.
    • gRPC endpoint added for cross-module reference queries.
  • Chores

    • Protobuf breaking check moved to CI script and module list updated.
    • Avro registration and module configuration finalized.
  • Tests

    • New unit/integration tests for reference sync, deletion handlers, and publish guards.
  • Documentation

    • Progress and epic docs updated for Phase 2 reference/delete work.

Review Change Stack

Co-authored-by: Phuong Nguyen <phuongnse@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6b3b889d-9c35-4e67-b1db-cdb875514cc9

📥 Commits

Reviewing files that changed from the base of the PR and between 5742850 and 1b566ed.

📒 Files selected for processing (1)
  • docs/playbooks/agent-checklist.md

📝 Walkthrough

Walkthrough

Adds WorkflowBuilder read models + EF migration, a Sync service and repository, gRPC proto/service for counting blocking form refs, FormBuilder deletion guard via gRPC, Kafka handlers to mark broken refs, DI/API and test updates, CI buf script, schema registration, and docs updates.

Changes

Workflow Reference Tracking and Deletion Guards

Layer / File(s) Summary
gRPC contract, proto tooling, and Avro schema
src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Contracts/Protos/..., src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Contracts/Axis.WorkflowBuilder.Contracts.csproj, src/Modules/FormBuilder/Axis.FormBuilder.Contracts/..., buf.yaml
Adds WorkflowFormReferenceService proto, enables protobuf/grpc generation, and adds FormDeletedEvent.avsc and FormBuilderKafkaTopics.FormDeleted topic constant.
Domain read models and extensions
src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Domain/ReadModels/*, src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Domain/ValueObjects/WorkflowTriggerExtensions.cs
Adds WorkflowFormReference and WorkflowModelReference read models with state transitions; adds TryGetEventModelId() to extract model IDs from event triggers and tightens formId parsing (Guid.Empty rejection).
EF Core migration and persistence configuration
src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Migrations/*, src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Persistence/*
Adds AddWorkflowReferenceReadModels migration, EF entity configurations, DbSet properties, composite keys and indexes, and updates model snapshot/designer.
Repository and sync contracts
src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application/Repositories/*, src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application/Services/*
Adds IWorkflowReferenceRepository, IWorkflowReferenceSync, and WorkflowReferenceSyncResult public contracts.
Sync implementation
src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Services/WorkflowReferenceSync.cs
Implements reconciliation of form/model reference read models and computes HasBrokenReferences from in-memory tracked entities.
Repository implementation
src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Repositories/WorkflowReferenceRepository.cs
Implements queries: broken refs check, blocking form references count, and retrieval of broken step/model ID sets.
Workflow command handlers with reference sync
src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application/Commands/*
Injects IWorkflowReferenceSync into workflow-modifying handlers and calls SyncAsync(workflow) before saving; PublishWorkflowHandler aborts publish when sync reports broken references.
GetWorkflow enrichment and DTO changes
src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application/Queries/GetWorkflow/*
GetWorkflow now queries broken step and model IDs and sets WorkflowStepDto.IsBroken and WorkflowTriggerDto.IsBroken.
gRPC service implementation and DI mapping
src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Grpc/*, src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Extensions/*
Implements WorkflowFormReferenceGrpcService.CountBlockingFormReferences, registers gRPC, adds MapWorkflowBuilderGrpc() with authorization, and adds project/protobuf wiring.
FormBuilder deletion guard and integration
src/Modules/FormBuilder/Axis.FormBuilder.Application/Services/IFormDeletionGuard.cs, src/Modules/FormBuilder/Axis.FormBuilder.Infrastructure/Grpc/FormWorkflowDeletionGuard.cs, src/Modules/FormBuilder/Axis.FormBuilder.Application/Commands/DeleteForm/DeleteFormHandler.cs, src/Modules/FormBuilder/Axis.FormBuilder.Infrastructure/Extensions/FormBuilderInfrastructureExtensions.cs
Adds IFormDeletionGuard and FormWorkflowDeletionGuard which calls the WorkflowBuilder gRPC API (forwards auth metadata, deadline, maps RPC errors to business-rule failures); DeleteFormHandler delegates validation to the guard; register gRPC client and configuration.
Kafka handlers and event mapping
src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Handlers/*, src/Modules/FormBuilder/Axis.FormBuilder.Infrastructure/Messaging/FormBuilderEventMapper.cs
Adds FormDeletedHandler and ModelDeletedHandler to mark matching references broken and persists changes; maps domain FormDeleted to Avro contract.
API wiring and settings
src/Axis.Api/Program.cs, src/Axis.Api/appsettings.json, tests/Api/Axis.Api.Tests/Helpers/ApiTestFixture.cs
Registers Avro publish/listen and local publish for FormDeletedEvent, calls app.MapWorkflowBuilderGrpc(), adds Modules:WorkflowBuilder:GrpcUrl to settings and test fixture, and registers test gRPC client.
CI buf breaking script and schema registration
.github/workflows/build-and-test.yml, scripts/buf-breaking-against-base.sh, scripts/register-avro-schemas.sh
Replaces inline buf-breaking step with scripts/buf-breaking-against-base.sh; updates schema registration to include FormDeletedEvent.
Tests — unit and integration
tests/*
Unit tests updated to mock IWorkflowReferenceSync/IWorkflowReferenceRepository and IFormDeletionGuard; adds PublishWorkflow test for broken refs; adds integration tests for FormDeletedHandler, ModelDeletedHandler, and WorkflowReferenceSync.
Documentation and playbook
docs/*
Updates PROGRESS, epic READMEs, feature docs, and playbook to reflect completed reference-tracking infrastructure, deferred items, and review guidance changes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • phuong-labs/axis#115: Related to CI buf and proto module registration changes touching build-and-test workflow and buf.yaml.
  • phuong-labs/axis#91: Related to Avro/schema registry registration and scripts; this PR adds FormDeletedEvent registration.
  • phuong-labs/axis#24: Prior work on WorkflowBuilder handlers that this change extends by adding reference-sync calls and gating.

"🐰 I nibbled through the proto trees,
counted refs and guarded keys,
gRPC whispers, Kafka breeze,
broken links now put at ease."

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/workflowbuilder-broken-refs-23c6

cursoragent and others added 2 commits May 25, 2026 08:00
Co-authored-by: Phuong Nguyen <phuongnse@users.noreply.github.com>
Co-authored-by: Phuong Nguyen <phuongnse@users.noreply.github.com>
@phuongnse phuongnse marked this pull request as ready for review May 25, 2026 08:15

@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.

Actionable comments posted: 5

🧹 Nitpick comments (9)
docs/PROGRESS.md (1)

50-54: ⚡ Quick win

Verify if WorkflowBuilder service-boundary retrofit status should be ✅

Line 50 marks WorkflowBuilder's "Service-boundary retrofit" as ⚠️, but line 54 describes the completed broken-refs work (read models, Kafka handlers, gRPC service, publish blocking, GetWorkflow enrichment) as ✅. If all the Phase 2 WorkflowBuilder contract work described in the PR objectives is now complete, the retrofit status should be updated to ✅ rather than ⚠️.

🤖 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 `@docs/PROGRESS.md` around lines 50 - 54, The PROGRESS.md entry incorrectly
leaves "Service-boundary retrofit" for WorkflowBuilder marked as ⚠️ while the
diff shows Phase 2 work completed (workflow_form_references +
workflow_model_references read models, ModelDeletedHandler, FormDeletedHandler
Kafka handlers, WorkflowFormReferenceService gRPC, publish blocking on broken
refs, and GetWorkflow exposing isBroken). Update the WorkflowBuilder
"Service-boundary retrofit" status from ⚠️ to ✅, ensure the single-line summary
near "WorkflowBuilder" reflects completion, and run a quick grep for the symbols
WorkflowBuilder, workflow_form_references, workflow_model_references,
ModelDeletedHandler, FormDeletedHandler, WorkflowFormReferenceService, and
GetWorkflow to confirm no other inconsistent status lines remain.
tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/AddStepHandlerTests.cs (1)

20-23: ⚡ Quick win

Assert reference-sync behavior in the success path.

The new dependency is injected but never verified, so a regression where sync is skipped would still pass. Please add an assertion in the happy-path test that the reference-sync call is executed once before commit.

🤖 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
`@tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/AddStepHandlerTests.cs`
around lines 20 - 23, Add an assertion in the happy-path test to verify the
injected IWorkflowReferenceSync (_referenceSync) is actually called once and
before the unit-of-work commit: use NSubstitute to assert the appropriate sync
method on _referenceSync was invoked exactly once and then assert call ordering
(e.g., Received.InOrder) that the _referenceSync call occurs prior to
_uow.Commit/CommitAsync; locate this in the AddStepHandlerTests happy-path test
that exercises AddStepHandler so the regression of skipping sync will fail.
tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/ConfigureStepHandlerTests.cs (1)

20-23: ⚡ Quick win

Validate that step reconfiguration triggers reference synchronization.

The constructor wiring is updated, but no test currently checks the sync call. Add a success-path interaction assertion on IWorkflowReferenceSync to cover the new contract.

🤖 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
`@tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/ConfigureStepHandlerTests.cs`
around lines 20 - 23, Add a test (or update the existing success-path test in
ConfigureStepHandlerTests) that asserts the ConfigureStepHandler triggers
reference synchronization by verifying the IWorkflowReferenceSync mock is
called; after invoking ConfigureStepHandler.Handle (or the test's existing act
step) use NSubstitute's Received(1) against the _referenceSync instance to
assert a call to its SyncReferenceAsync (or the appropriate sync method) with
the expected workflow id/step arguments to cover the new contract.
tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/AddTriggerHandlerTests.cs (1)

19-22: ⚡ Quick win

Add a verification that trigger updates invoke reference sync.

Since IWorkflowReferenceSync is now part of the handler contract, add a success-path assertion for its invocation to protect against silent regressions.

🤖 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
`@tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/AddTriggerHandlerTests.cs`
around lines 19 - 22, Add a success-path assertion in AddTriggerHandlerTests to
verify the handler calls the IWorkflowReferenceSync dependency: after invoking
AddTriggerHandler.Handle (via _handler) on the test request, assert that the
mock _referenceSync received the expected call (e.g., Received(1)) to its sync
method so the test validates that AddTriggerHandler invokes
IWorkflowReferenceSync on success.
tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/RemoveTriggerHandlerTests.cs (1)

19-22: ⚡ Quick win

Add assertions for the newly injected IWorkflowReferenceSync dependency.

Please verify expected sync calls on success and absence of calls on not-found paths so this handler’s new side-effect contract is explicitly protected by tests.

🤖 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
`@tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/RemoveTriggerHandlerTests.cs`
around lines 19 - 22, Add assertions in RemoveTriggerHandlerTests to verify
calls to the injected IWorkflowReferenceSync (_referenceSync) for both success
and not-found scenarios: for the successful removal test that uses
RemoveTriggerHandler, assert that _referenceSync.Sync (or the actual sync method
name on IWorkflowReferenceSync used by RemoveTriggerHandler) was called with the
expected workflow/trigger identifiers; for the not-found test assert that
_referenceSync received no calls (e.g. DidNotReceive) to ensure no side-effects.
Update the existing success test to include the positive assertion and the
not-found test to include the negative assertion referencing the _referenceSync
field and the handler under test.
tests/Modules/FormBuilder/Axis.FormBuilder.Application.Tests/Commands/DeleteFormHandlerTests.cs (1)

15-21: ⚡ Quick win

Strengthen failure-path interaction assertions for IFormDeletionGuard.

Since guard-based behavior is now central, add assertions that guard is not called when form lookup fails, and SaveChangesAsync is not called when guard returns a business-rule failure.

Proposed assertion additions
 [Fact]
 public async Task DeleteForm_WhenReferencedByActiveWorkflow_ReturnsBusinessRuleFailure()
 {
@@
     result.IsFailure.Should().BeTrue();
     result.ErrorCode.Should().Be(ErrorCodes.BusinessRule);
     result.Error.Should().Contain("workflow");
+    await _uow.DidNotReceive().SaveChangesAsync(Arg.Any<CancellationToken>());
 }

 [Fact]
 public async Task DeleteForm_WhenFormNotFound_ReturnsNotFound()
 {
@@
     result.IsFailure.Should().BeTrue();
     result.ErrorCode.Should().Be(ErrorCodes.NotFound);
     result.Error.Should().Contain("not found");
+    await _formDeletionGuard.DidNotReceive()
+        .ValidateCanDeleteAsync(Arg.Any<Guid>(), Arg.Any<Guid>(), Arg.Any<CancellationToken>());
 }

Also applies to: 43-45

🤖 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
`@tests/Modules/FormBuilder/Axis.FormBuilder.Application.Tests/Commands/DeleteFormHandlerTests.cs`
around lines 15 - 21, Update the tests in DeleteFormHandlerTests to assert guard
and uow interactions on failure paths: in the test where the form lookup returns
null, add an assertion that _formDeletionGuard.DidNotReceive() was called (i.e.,
ensure CreateHandler/handler does not invoke _formDeletionGuard); and in the
test where the guard returns a business-rule failure, assert that
_uow.DidNotReceive().SaveChangesAsync(Arg.Any<CancellationToken>()) is called
(i.e., ensure SaveChangesAsync is not invoked when _formDeletionGuard signals
failure). Locate these checks around the existing Arrange/Act/Assert blocks that
use CreateHandler(), _formDeletionGuard, _formRepo and _uow and add the
DidNotReceive assertions accordingly.
tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/ImportWorkflowHandlerTests.cs (1)

20-23: ⚡ Quick win

Assert IWorkflowReferenceSync interaction in success and conflict paths.

With _referenceSync newly injected, add assertions that it runs on successful imports and is not called when NameExistsAsync short-circuits. That locks the new dependency contract in this suite.

🤖 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
`@tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/ImportWorkflowHandlerTests.cs`
around lines 20 - 23, Add assertions in ImportWorkflowHandlerTests to verify the
new _referenceSync dependency is invoked on the successful import path and not
invoked on the NameExistsAsync short-circuit (conflict) path: after exercising
ImportWorkflowHandler (the _handler) in the success test assert
_referenceSync.Received(...) was called once with the expected arguments using
NSubstitute Received, and in the conflict test assert
_referenceSync.DidNotReceive() was not called; locate these asserts next to the
existing success and conflict test arrangements where NameExistsAsync is faked
to succeed or to short-circuit so the interaction contract is locked into the
suite.
tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/RemoveStepHandlerTests.cs (1)

20-23: ⚡ Quick win

Cover the new reference-sync collaborator contract in this test class.

Now that _referenceSync is injected, add assertions for expected calls on successful removal and no calls on early-return failures. This prevents silent regressions where step deletion stops updating reference state.

🤖 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
`@tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/RemoveStepHandlerTests.cs`
around lines 20 - 23, The tests need to assert interactions with the newly
injected _referenceSync collaborator: update the success-case test(s) in
RemoveStepHandlerTests (which exercise RemoveStepHandler) to assert that the
substitute _referenceSync received the expected call (e.g., Received(1) with the
removed step id or workflow id) after a successful removal, and update the
failure/early-return tests (not-found/validation-failure) to assert
_referenceSync.DidNotReceiveAnyCalls() (or DidNotReceive() for the specific
reference method) to ensure no reference-sync happens on early exit; use the
same unique symbols (_referenceSync and RemoveStepHandler) and verify parameters
match the step/workflow used in the test.
tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/GetWorkflowHandlerTests.cs (1)

16-19: ⚡ Quick win

Add short-circuit assertions for the new reference-repository dependency.

_referenceRepo is now part of handler wiring, but the not-found paths don’t assert it is skipped. Please assert no _referenceRepo calls when workflow lookup returns null so extra reference queries can’t regress in later refactors.

Proposed test assertions
 [Fact]
 public async Task Handle_WhenWorkflowNotFound_ReturnsNull()
 {
     _repo.GetByIdAsync(Arg.Any<Guid>(), OrgId, Arg.Any<CancellationToken>()).Returns((WorkflowDefinition?)null);

     WorkflowDetailDto? dto = await _handler.Handle(
         new GetWorkflowQuery(Guid.NewGuid(), OrgId), CancellationToken.None);

     dto.Should().BeNull();
+    await _referenceRepo.DidNotReceive()
+        .GetBrokenStepIdsAsync(Arg.Any<Guid>(), Arg.Any<CancellationToken>());
+    await _referenceRepo.DidNotReceive()
+        .HasBrokenEventTriggerAsync(Arg.Any<Guid>(), Arg.Any<CancellationToken>());
 }
🤖 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
`@tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/GetWorkflowHandlerTests.cs`
around lines 16 - 19, The not-found-path tests need to assert that the new
_referenceRepo dependency is not queried when the workflow lookup returns null;
update the GetWorkflowHandlerTests not-found test(s) to verify no calls were
made to _referenceRepo (e.g. assert
_referenceRepo.ReceivedCalls().Should().BeEmpty() or use NSubstitute’s
DidNotReceive/DidNotReceiveWithAnyArgs on the relevant repository methods) after
arranging _repo to return null and invoking GetWorkflowHandler.Handle; reference
the field _referenceRepo and the class GetWorkflowHandler in the assertion so
future refactors can’t introduce unexpected reference lookups.
🤖 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
`@src/Modules/FormBuilder/Axis.FormBuilder.Infrastructure/Grpc/FormWorkflowDeletionGuard.cs`:
- Around line 25-33: The RPC call in
FormWorkflowDeletionGuard.ValidateCanDeleteAsync to
CountBlockingFormReferencesAsync has no per-call deadline; wrap the call in a
short bounded timeout by creating a linked CancellationTokenSource with a small
timeout and passing its token to CountBlockingFormReferencesAsync (use the
existing headers and the new token) so the delete path can’t hang indefinitely,
and update the RpcException handling in ValidateCanDeleteAsync to treat
StatusCode.DeadlineExceeded the same as StatusCode.Unavailable (i.e., consider
it transient/unavailable) when deciding retry/abort logic.

In
`@src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application/Commands/PublishWorkflow/PublishWorkflowHandler.cs`:
- Around line 24-31: PublishWorkflowHandler currently calls
referenceSync.SyncAsync(workflow, cancellationToken) but then checks
WorkflowReferenceRepository.HasBrokenReferencesAsync(workflow.Id, ...) which
queries the DB and therefore may see stale IsBroken values; after SyncAsync you
must either persist the sync results before re-checking or make the check
operate on the in-memory tracked references. Fix by calling the unit-of-work
save (uow.SaveChangesAsync(cancellationToken)) immediately after
referenceSync.SyncAsync(...) and before calling HasBrokenReferencesAsync, or
alternatively update WorkflowReferenceRepository.HasBrokenReferencesAsync to
examine the tracked WorkflowFormReferences/WorkflowModelReferences on the
DbContext/ChangeTracker for the given workflow Id so it reflects the fresh
in-memory IsBroken state.

In
`@src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Domain/ValueObjects/WorkflowTriggerExtensions.cs`:
- Around line 10-13: The extension method TryGetEventModelId should guard
against a null extension receiver: in WorkflowTriggerExtensions.cs add an early
null check for the trigger (e.g., if (trigger == null) return null;) before
accessing trigger.Type or trigger.Config so the method safely returns null when
called on a null WorkflowTrigger.

In
`@src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Persistence/Configurations/WorkflowModelReferenceConfiguration.cs`:
- Line 12: The primary key defined in WorkflowModelReferenceConfiguration using
builder.HasKey(r => r.WorkflowId) is too narrow (only allows one reference per
workflow); change the entity key to a composite key that uniquely identifies
each model reference (e.g., include the model/reference identifier property such
as ModelId or ReferenceId along with WorkflowId) by updating the HasKey(...)
call in WorkflowModelReferenceConfiguration and ensure the corresponding entity
has those properties configured as required, then regenerate the EF Core
migration and snapshot so the database constraints match the new composite key.

In
`@src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Services/WorkflowReferenceSync.cs`:
- Around line 47-50: When the referenced ID hasn't changed the code currently
calls row.MarkHealthy() unconditionally and can clear a previously broken state;
instead, only clear IsBroken after verifying the target actually exists. In the
block handling unchanged IDs (where you see row.FormId, formId.Value and
row.MarkHealthy()), replace the unconditional MarkHealthy() with a check that
the target resource for formId.Value exists (use your repository/lookup used
elsewhere in this class) and only call row.MarkHealthy() if that existence check
succeeds; otherwise leave the row as broken (do not clear IsBroken) so
publish/guard logic remains correct. Ensure the same change is applied to the
other occurrence around lines 74-77 where row.MarkHealthy() is called.

---

Nitpick comments:
In `@docs/PROGRESS.md`:
- Around line 50-54: The PROGRESS.md entry incorrectly leaves "Service-boundary
retrofit" for WorkflowBuilder marked as ⚠️ while the diff shows Phase 2 work
completed (workflow_form_references + workflow_model_references read models,
ModelDeletedHandler, FormDeletedHandler Kafka handlers,
WorkflowFormReferenceService gRPC, publish blocking on broken refs, and
GetWorkflow exposing isBroken). Update the WorkflowBuilder "Service-boundary
retrofit" status from ⚠️ to ✅, ensure the single-line summary near
"WorkflowBuilder" reflects completion, and run a quick grep for the symbols
WorkflowBuilder, workflow_form_references, workflow_model_references,
ModelDeletedHandler, FormDeletedHandler, WorkflowFormReferenceService, and
GetWorkflow to confirm no other inconsistent status lines remain.

In
`@tests/Modules/FormBuilder/Axis.FormBuilder.Application.Tests/Commands/DeleteFormHandlerTests.cs`:
- Around line 15-21: Update the tests in DeleteFormHandlerTests to assert guard
and uow interactions on failure paths: in the test where the form lookup returns
null, add an assertion that _formDeletionGuard.DidNotReceive() was called (i.e.,
ensure CreateHandler/handler does not invoke _formDeletionGuard); and in the
test where the guard returns a business-rule failure, assert that
_uow.DidNotReceive().SaveChangesAsync(Arg.Any<CancellationToken>()) is called
(i.e., ensure SaveChangesAsync is not invoked when _formDeletionGuard signals
failure). Locate these checks around the existing Arrange/Act/Assert blocks that
use CreateHandler(), _formDeletionGuard, _formRepo and _uow and add the
DidNotReceive assertions accordingly.

In
`@tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/AddStepHandlerTests.cs`:
- Around line 20-23: Add an assertion in the happy-path test to verify the
injected IWorkflowReferenceSync (_referenceSync) is actually called once and
before the unit-of-work commit: use NSubstitute to assert the appropriate sync
method on _referenceSync was invoked exactly once and then assert call ordering
(e.g., Received.InOrder) that the _referenceSync call occurs prior to
_uow.Commit/CommitAsync; locate this in the AddStepHandlerTests happy-path test
that exercises AddStepHandler so the regression of skipping sync will fail.

In
`@tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/AddTriggerHandlerTests.cs`:
- Around line 19-22: Add a success-path assertion in AddTriggerHandlerTests to
verify the handler calls the IWorkflowReferenceSync dependency: after invoking
AddTriggerHandler.Handle (via _handler) on the test request, assert that the
mock _referenceSync received the expected call (e.g., Received(1)) to its sync
method so the test validates that AddTriggerHandler invokes
IWorkflowReferenceSync on success.

In
`@tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/ConfigureStepHandlerTests.cs`:
- Around line 20-23: Add a test (or update the existing success-path test in
ConfigureStepHandlerTests) that asserts the ConfigureStepHandler triggers
reference synchronization by verifying the IWorkflowReferenceSync mock is
called; after invoking ConfigureStepHandler.Handle (or the test's existing act
step) use NSubstitute's Received(1) against the _referenceSync instance to
assert a call to its SyncReferenceAsync (or the appropriate sync method) with
the expected workflow id/step arguments to cover the new contract.

In
`@tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/GetWorkflowHandlerTests.cs`:
- Around line 16-19: The not-found-path tests need to assert that the new
_referenceRepo dependency is not queried when the workflow lookup returns null;
update the GetWorkflowHandlerTests not-found test(s) to verify no calls were
made to _referenceRepo (e.g. assert
_referenceRepo.ReceivedCalls().Should().BeEmpty() or use NSubstitute’s
DidNotReceive/DidNotReceiveWithAnyArgs on the relevant repository methods) after
arranging _repo to return null and invoking GetWorkflowHandler.Handle; reference
the field _referenceRepo and the class GetWorkflowHandler in the assertion so
future refactors can’t introduce unexpected reference lookups.

In
`@tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/ImportWorkflowHandlerTests.cs`:
- Around line 20-23: Add assertions in ImportWorkflowHandlerTests to verify the
new _referenceSync dependency is invoked on the successful import path and not
invoked on the NameExistsAsync short-circuit (conflict) path: after exercising
ImportWorkflowHandler (the _handler) in the success test assert
_referenceSync.Received(...) was called once with the expected arguments using
NSubstitute Received, and in the conflict test assert
_referenceSync.DidNotReceive() was not called; locate these asserts next to the
existing success and conflict test arrangements where NameExistsAsync is faked
to succeed or to short-circuit so the interaction contract is locked into the
suite.

In
`@tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/RemoveStepHandlerTests.cs`:
- Around line 20-23: The tests need to assert interactions with the newly
injected _referenceSync collaborator: update the success-case test(s) in
RemoveStepHandlerTests (which exercise RemoveStepHandler) to assert that the
substitute _referenceSync received the expected call (e.g., Received(1) with the
removed step id or workflow id) after a successful removal, and update the
failure/early-return tests (not-found/validation-failure) to assert
_referenceSync.DidNotReceiveAnyCalls() (or DidNotReceive() for the specific
reference method) to ensure no reference-sync happens on early exit; use the
same unique symbols (_referenceSync and RemoveStepHandler) and verify parameters
match the step/workflow used in the test.

In
`@tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/RemoveTriggerHandlerTests.cs`:
- Around line 19-22: Add assertions in RemoveTriggerHandlerTests to verify calls
to the injected IWorkflowReferenceSync (_referenceSync) for both success and
not-found scenarios: for the successful removal test that uses
RemoveTriggerHandler, assert that _referenceSync.Sync (or the actual sync method
name on IWorkflowReferenceSync used by RemoveTriggerHandler) was called with the
expected workflow/trigger identifiers; for the not-found test assert that
_referenceSync received no calls (e.g. DidNotReceive) to ensure no side-effects.
Update the existing success test to include the positive assertion and the
not-found test to include the negative assertion referencing the _referenceSync
field and the handler under test.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6df3a678-6f16-4ada-8d76-7346eaaf9107

📥 Commits

Reviewing files that changed from the base of the PR and between f6094df and aa0f6ea.

⛔ Files ignored due to path filters (1)
  • src/Modules/FormBuilder/Axis.FormBuilder.Contracts/Generated/axis/formbuilder/events/FormDeletedEvent.cs is excluded by !**/generated/**
📒 Files selected for processing (63)
  • .github/workflows/build-and-test.yml
  • buf.yaml
  • docs/PROGRESS.md
  • docs/epics/E03-data-modeling/README.md
  • docs/epics/E03-data-modeling/features/F01-model-definition.md
  • docs/epics/E04-workflow-builder/README.md
  • docs/epics/E05-form-builder/README.md
  • docs/epics/README.md
  • scripts/buf-breaking-against-base.sh
  • scripts/register-avro-schemas.sh
  • src/Axis.Api/Program.cs
  • src/Axis.Api/appsettings.json
  • src/Modules/FormBuilder/Axis.FormBuilder.Application/Commands/DeleteForm/DeleteFormHandler.cs
  • src/Modules/FormBuilder/Axis.FormBuilder.Application/Services/IFormDeletionGuard.cs
  • src/Modules/FormBuilder/Axis.FormBuilder.Contracts/FormBuilderEventExtensions.cs
  • src/Modules/FormBuilder/Axis.FormBuilder.Contracts/FormBuilderKafkaTopics.cs
  • src/Modules/FormBuilder/Axis.FormBuilder.Contracts/Schemas/FormDeletedEvent.avsc
  • src/Modules/FormBuilder/Axis.FormBuilder.Infrastructure/Extensions/FormBuilderInfrastructureExtensions.cs
  • src/Modules/FormBuilder/Axis.FormBuilder.Infrastructure/Grpc/FormWorkflowDeletionGuard.cs
  • src/Modules/FormBuilder/Axis.FormBuilder.Infrastructure/Messaging/FormBuilderEventMapper.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application/Commands/AddStep/AddStepHandler.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application/Commands/AddTrigger/AddTriggerHandler.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application/Commands/ConfigureStep/ConfigureStepHandler.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application/Commands/ImportWorkflow/ImportWorkflowHandler.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application/Commands/PublishWorkflow/PublishWorkflowHandler.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application/Commands/RemoveStep/RemoveStepHandler.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application/Commands/RemoveTrigger/RemoveTriggerHandler.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application/Queries/GetWorkflow/GetWorkflowHandler.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application/Queries/GetWorkflow/WorkflowStepDto.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application/Queries/GetWorkflow/WorkflowTriggerDto.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application/Repositories/IWorkflowReferenceRepository.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application/Services/IWorkflowReferenceSync.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Contracts/Axis.WorkflowBuilder.Contracts.csproj
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Contracts/Protos/axis/workflowbuilder/v1/workflow_form_reference_service.proto
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Domain/ReadModels/WorkflowFormReference.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Domain/ReadModels/WorkflowModelReference.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Domain/ValueObjects/WorkflowTriggerExtensions.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/AssemblyInfo.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Axis.WorkflowBuilder.Infrastructure.csproj
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Extensions/WorkflowBuilderInfrastructureExtensions.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Grpc/WorkflowFormReferenceGrpcService.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Handlers/FormDeletedHandler.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Handlers/ModelDeletedHandler.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Migrations/20260525120000_AddWorkflowReferenceReadModels.Designer.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Migrations/20260525120000_AddWorkflowReferenceReadModels.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Migrations/WorkflowBuilderDbContextModelSnapshot.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Persistence/Configurations/WorkflowFormReferenceConfiguration.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Persistence/Configurations/WorkflowModelReferenceConfiguration.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Persistence/WorkflowBuilderDbContext.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Repositories/WorkflowReferenceRepository.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Services/WorkflowReferenceSync.cs
  • tests/Api/Axis.Api.Tests/Helpers/ApiTestFixture.cs
  • tests/Modules/FormBuilder/Axis.FormBuilder.Application.Tests/Commands/DeleteFormHandlerTests.cs
  • tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/AddStepHandlerTests.cs
  • tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/AddTriggerHandlerTests.cs
  • tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/Commands/PublishWorkflowHandlerTests.cs
  • tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/ConfigureStepHandlerTests.cs
  • tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/GetWorkflowHandlerTests.cs
  • tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/ImportWorkflowHandlerTests.cs
  • tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/RemoveStepHandlerTests.cs
  • tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/RemoveTriggerHandlerTests.cs
  • tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure.Tests/Handlers/FormDeletedHandlerTests.cs
  • tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure.Tests/Handlers/ModelDeletedHandlerTests.cs

Co-authored-by: Phuong Nguyen <phuongnse@users.noreply.github.com>

@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.

Actionable comments posted: 2

🤖 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
`@src/Modules/FormBuilder/Axis.FormBuilder.Infrastructure/Grpc/FormWorkflowDeletionGuard.cs`:
- Around line 44-49: In ValidateCanDeleteAsync, preserve caller cancellation by
checking cancellationToken.IsCancellationRequested when catching RpcException
with StatusCode.Cancelled (and treat DeadlineExceeded as before); if the token
is cancelled rethrow or call cancellationToken.ThrowIfCancellationRequested() so
an OperationCanceledException propagates to the caller, otherwise continue to
return Result.Failure for non-caller cancellations/timeouts (keep the existing
message for StatusCode.DeadlineExceeded and non-caller Cancelled cases).

In
`@src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Services/WorkflowReferenceSync.cs`:
- Around line 58-62: The current LINQ that builds currentModelIds from
workflow.Triggers via TryGetEventModelId() does not exclude Guid.Empty, allowing
invalid workflow_model_references to persist; update the chain in
WorkflowReferenceSync (the currentModelIds variable) to filter out empty GUIDs
(e.g., require id.HasValue && id.Value != Guid.Empty or use
id.GetValueOrDefault() != Guid.Empty) before selecting id.Value into the HashSet
so only valid non-empty GUIDs are included.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7771a938-5db1-43cc-9fa3-a940f75c8ce7

📥 Commits

Reviewing files that changed from the base of the PR and between aa0f6ea and feaebe3.

📒 Files selected for processing (13)
  • src/Modules/FormBuilder/Axis.FormBuilder.Infrastructure/Grpc/FormWorkflowDeletionGuard.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application/Commands/PublishWorkflow/PublishWorkflowHandler.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application/Queries/GetWorkflow/GetWorkflowHandler.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application/Repositories/IWorkflowReferenceRepository.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Domain/ValueObjects/WorkflowTriggerExtensions.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Migrations/20260525120000_AddWorkflowReferenceReadModels.Designer.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Migrations/20260525120000_AddWorkflowReferenceReadModels.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Migrations/WorkflowBuilderDbContextModelSnapshot.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Persistence/Configurations/WorkflowModelReferenceConfiguration.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Repositories/WorkflowReferenceRepository.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Services/WorkflowReferenceSync.cs
  • tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/Commands/PublishWorkflowHandlerTests.cs
  • tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/GetWorkflowHandlerTests.cs
✅ Files skipped from review due to trivial changes (2)
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Migrations/WorkflowBuilderDbContextModelSnapshot.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Migrations/20260525120000_AddWorkflowReferenceReadModels.Designer.cs

cursoragent and others added 3 commits May 25, 2026 08:52
Co-authored-by: Phuong Nguyen <phuongnse@users.noreply.github.com>
Co-authored-by: Phuong Nguyen <phuongnse@users.noreply.github.com>
Co-authored-by: Phuong Nguyen <phuongnse@users.noreply.github.com>

@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.

♻️ Duplicate comments (1)
src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Services/WorkflowReferenceSync.cs (1)

85-89: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Exclude Guid.Empty from synchronized model references.

Line [87]-Line [89] still permits Guid.Empty model IDs, which can create invalid workflow_model_references rows and skew broken-reference checks.

🔧 Proposed fix
         HashSet<Guid> currentModelIds = workflow.Triggers
             .Select(t => t.TryGetEventModelId())
-            .Where(id => id.HasValue)
+            .Where(id => id.HasValue && id.Value != Guid.Empty)
             .Select(id => id!.Value)
             .ToHashSet();
🤖 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/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Services/WorkflowReferenceSync.cs`
around lines 85 - 89, The currentModelIds set is including Guid.Empty from
workflow.Triggers via TryGetEventModelId, which allows invalid
workflow_model_references; update the LINQ pipeline that builds currentModelIds
(the expression using workflow.Triggers, TryGetEventModelId, and ToHashSet) to
exclude Guid.Empty by filtering out nulls and Guid.Empty (e.g., require
id.HasValue && id.Value != Guid.Empty or filter after the Select) so only valid
non-empty GUIDs are added to currentModelIds before synchronization.
🧹 Nitpick comments (3)
tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/RemoveStepHandlerTests.cs (1)

22-28: ⚡ Quick win

Assert IWorkflowReferenceSync.SyncAsync behavior explicitly in handler tests.

The new dependency is stubbed, but tests still don’t verify it was actually called on success (and skipped on early-failure paths). Adding those assertions will make this change resilient to regressions.

Suggested test assertions
@@
     public async Task Handle_WhenStepExists_RemovesStepAndSaves()
@@
         result.IsSuccess.Should().BeTrue();
         wf.Steps.Should().NotContain(s => s.Id == step.Id);
+        await _referenceSync.Received(1)
+            .SyncAsync(wf, Arg.Any<CancellationToken>());
         await _uow.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
@@
     public async Task Handle_WhenWorkflowNotFound_ReturnsNotFound()
@@
         result.IsSuccess.Should().BeFalse();
         result.ErrorCode.Should().Be(ErrorCodes.NotFound);
+        await _referenceSync.DidNotReceive()
+            .SyncAsync(Arg.Any<WorkflowDefinition>(), Arg.Any<CancellationToken>());
🤖 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
`@tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/RemoveStepHandlerTests.cs`
around lines 22 - 28, Update the RemoveStepHandlerTests to explicitly assert
calls to the IWorkflowReferenceSync mock: in successful-path tests verify
_referenceSync.SyncAsync was called with the expected WorkflowDefinition and any
CancellationToken (use Arg.Is or equivalent) after invoking
RemoveStepHandler.Handle, and in early-failure/path tests verify
_referenceSync.SyncAsync was NOT called; locate usages in the
RemoveStepHandlerTests constructor/setup (where _referenceSync is stubbed) and
add these call/verifications surrounding calls to RemoveStepHandler.Handle to
ensure SyncAsync is invoked only on success.
tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/ImportWorkflowHandlerTests.cs (1)

22-28: ⚡ Quick win

Strengthen these tests by asserting the sync call contract.

IWorkflowReferenceSync is now part of the import flow, but current assertions don’t validate that call. Add positive/negative interaction checks to lock in expected orchestration.

Suggested test assertions
@@
     public async Task Handle_WhenNameIsAvailable_CreatesNewDraftWorkflowAndSaves()
@@
         await _repo.Received(1).AddAsync(
             Arg.Is<WorkflowDefinition>(w =>
                 w.Name == exportDto.Name &&
                 w.Status == WorkflowStatus.Draft &&
                 w.OrganizationId == OrgId),
             Arg.Any<CancellationToken>());
+        await _referenceSync.Received(1)
+            .SyncAsync(Arg.Any<WorkflowDefinition>(), Arg.Any<CancellationToken>());
         await _uow.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
@@
     public async Task Handle_WhenNameAlreadyExists_ReturnsConflict()
@@
         result.IsSuccess.Should().BeFalse();
         result.ErrorCode.Should().Be(ErrorCodes.Conflict);
+        await _referenceSync.DidNotReceive()
+            .SyncAsync(Arg.Any<WorkflowDefinition>(), Arg.Any<CancellationToken>());
         await _uow.DidNotReceive().SaveChangesAsync(Arg.Any<CancellationToken>());
🤖 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
`@tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/ImportWorkflowHandlerTests.cs`
around lines 22 - 28, The tests currently stub IWorkflowReferenceSync.SyncAsync
but don’t assert it was called; update ImportWorkflowHandlerTests to verify the
orchestration by adding interaction assertions on the _referenceSync mock: use
_referenceSync.Received(1).SyncAsync(Arg.Is<WorkflowDefinition>(wd => /*
optional checks on expected fields */), Arg.Any<CancellationToken>()) in the
positive import test, and in negative/no-op cases assert
_referenceSync.DidNotReceive().SyncAsync(Arg.Any<WorkflowDefinition>(),
Arg.Any<CancellationToken>()); ensure these assertions are placed after invoking
the handler (e.g., after calling ImportWorkflowHandler.Handle/Execute) so the
test locks in the expected calls.
tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/RemoveTriggerHandlerTests.cs (1)

21-27: ⚡ Quick win

Add interaction assertions for SyncAsync now that it’s a required collaborator.

The constructor setup is good, but these tests should also assert SyncAsync is called on success and not called when workflow lookup fails.

Suggested test assertions
@@
     public async Task Handle_WhenTriggerExists_RemovesAndSaves()
@@
         result.IsSuccess.Should().BeTrue();
         wf.Triggers.Should().BeEmpty();
+        await _referenceSync.Received(1)
+            .SyncAsync(wf, Arg.Any<CancellationToken>());
         await _uow.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
@@
     public async Task Handle_WhenWorkflowNotFound_ReturnsNotFound()
@@
         result.IsSuccess.Should().BeFalse();
         result.ErrorCode.Should().Be(ErrorCodes.NotFound);
+        await _referenceSync.DidNotReceive()
+            .SyncAsync(Arg.Any<WorkflowDefinition>(), Arg.Any<CancellationToken>());
🤖 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
`@tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/RemoveTriggerHandlerTests.cs`
around lines 21 - 27, Add assertions in the tests to verify that the
collaborator _referenceSync.SyncAsync is invoked on successful removal and not
invoked when the workflow lookup fails: in the successful-path test use
_referenceSync.Received(1).SyncAsync(Arg.Any<WorkflowDefinition>(),
Arg.Any<CancellationToken>()) (or ReceivedWithAnyArgs if preferred) after
calling RemoveTriggerHandler.Handle/HandleAsync, and in the failure-path test
use _referenceSync.DidNotReceive().SyncAsync(Arg.Any<WorkflowDefinition>(),
Arg.Any<CancellationToken>()) to assert it was not called.
🤖 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.

Duplicate comments:
In
`@src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Services/WorkflowReferenceSync.cs`:
- Around line 85-89: The currentModelIds set is including Guid.Empty from
workflow.Triggers via TryGetEventModelId, which allows invalid
workflow_model_references; update the LINQ pipeline that builds currentModelIds
(the expression using workflow.Triggers, TryGetEventModelId, and ToHashSet) to
exclude Guid.Empty by filtering out nulls and Guid.Empty (e.g., require
id.HasValue && id.Value != Guid.Empty or filter after the Select) so only valid
non-empty GUIDs are added to currentModelIds before synchronization.

---

Nitpick comments:
In
`@tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/ImportWorkflowHandlerTests.cs`:
- Around line 22-28: The tests currently stub IWorkflowReferenceSync.SyncAsync
but don’t assert it was called; update ImportWorkflowHandlerTests to verify the
orchestration by adding interaction assertions on the _referenceSync mock: use
_referenceSync.Received(1).SyncAsync(Arg.Is<WorkflowDefinition>(wd => /*
optional checks on expected fields */), Arg.Any<CancellationToken>()) in the
positive import test, and in negative/no-op cases assert
_referenceSync.DidNotReceive().SyncAsync(Arg.Any<WorkflowDefinition>(),
Arg.Any<CancellationToken>()); ensure these assertions are placed after invoking
the handler (e.g., after calling ImportWorkflowHandler.Handle/Execute) so the
test locks in the expected calls.

In
`@tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/RemoveStepHandlerTests.cs`:
- Around line 22-28: Update the RemoveStepHandlerTests to explicitly assert
calls to the IWorkflowReferenceSync mock: in successful-path tests verify
_referenceSync.SyncAsync was called with the expected WorkflowDefinition and any
CancellationToken (use Arg.Is or equivalent) after invoking
RemoveStepHandler.Handle, and in early-failure/path tests verify
_referenceSync.SyncAsync was NOT called; locate usages in the
RemoveStepHandlerTests constructor/setup (where _referenceSync is stubbed) and
add these call/verifications surrounding calls to RemoveStepHandler.Handle to
ensure SyncAsync is invoked only on success.

In
`@tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/RemoveTriggerHandlerTests.cs`:
- Around line 21-27: Add assertions in the tests to verify that the collaborator
_referenceSync.SyncAsync is invoked on successful removal and not invoked when
the workflow lookup fails: in the successful-path test use
_referenceSync.Received(1).SyncAsync(Arg.Any<WorkflowDefinition>(),
Arg.Any<CancellationToken>()) (or ReceivedWithAnyArgs if preferred) after
calling RemoveTriggerHandler.Handle/HandleAsync, and in the failure-path test
use _referenceSync.DidNotReceive().SyncAsync(Arg.Any<WorkflowDefinition>(),
Arg.Any<CancellationToken>()) to assert it was not called.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 696fa366-4719-4c28-b105-6b722c773e98

📥 Commits

Reviewing files that changed from the base of the PR and between fe69bcd and 3f57e42.

📒 Files selected for processing (14)
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application/Commands/PublishWorkflow/PublishWorkflowHandler.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application/Repositories/IWorkflowReferenceRepository.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application/Services/IWorkflowReferenceSync.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application/Services/WorkflowReferenceSyncResult.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Repositories/WorkflowReferenceRepository.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Services/WorkflowReferenceSync.cs
  • tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/AddStepHandlerTests.cs
  • tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/AddTriggerHandlerTests.cs
  • tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/Commands/PublishWorkflowHandlerTests.cs
  • tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/ConfigureStepHandlerTests.cs
  • tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/ImportWorkflowHandlerTests.cs
  • tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/RemoveStepHandlerTests.cs
  • tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application.Tests/RemoveTriggerHandlerTests.cs
  • tests/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure.Tests/Services/WorkflowReferenceSyncTests.cs
✅ Files skipped from review due to trivial changes (1)
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Application/Services/WorkflowReferenceSyncResult.cs

Co-authored-by: Phuong Nguyen <phuongnse@users.noreply.github.com>

@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.

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 `@docs/playbooks/agent-checklist.md`:
- Line 129: Gate 3's retrospective question count is out of sync: you added an
eighth question ("Review fix band-aid (mid-command save / EF query hack)?") but
the summary still says "seven questions"; update the Gate 3 count text to "eight
questions" wherever it references "seven questions" (search for the string
"seven questions" and the "Gate 3" heading) so the checklist and count match.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f5e482c4-58f9-4207-8a5a-d05856451715

📥 Commits

Reviewing files that changed from the base of the PR and between 3f57e42 and 5742850.

📒 Files selected for processing (5)
  • docs/playbooks/agent-checklist.md
  • src/Modules/FormBuilder/Axis.FormBuilder.Infrastructure/Grpc/FormWorkflowDeletionGuard.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Domain/Entities/WorkflowStep.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Domain/ValueObjects/WorkflowTriggerExtensions.cs
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Infrastructure/Services/WorkflowReferenceSync.cs
✅ Files skipped from review due to trivial changes (1)
  • src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Domain/Entities/WorkflowStep.cs

Comment thread docs/playbooks/agent-checklist.md Outdated
Co-authored-by: Phuong Nguyen <phuongnse@users.noreply.github.com>
@phuongnse phuongnse merged commit df6ca36 into main May 25, 2026
8 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.

2 participants