feat(formbuilder): ModelDeletedEvent consumer for broken Relation Picker refs#102
Conversation
…er refs - form_model_references read model + sync on field add/remove - ModelDeletedHandler flags broken fields; GET form returns isBroken - Block DELETE model when active form references exist (US-033) - EF migration AddFormModelReferences Co-authored-by: Phuong Nguyen <phuongnse@users.noreply.github.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (11)
✅ Files skipped from review due to trivial changes (1)
📝 WalkthroughWalkthroughThis PR adds persistent form-model reference tracking (DB+EF migration), a ModelDeleted handler that flags broken references, a gRPC count API and FormModelDeletionGuard used by DataModeling to block deletions, refactors several Identity endpoints to use MediatR queries/handlers, and introduces architectural gateway tests. ChangesForm-Model Reference Tracking and Deletion Guard
Identity Module Mediator Refactoring
Architectural Enforcement
Sequence Diagram(s)sequenceDiagram
participant DataModeling
participant Kafka
participant ModelDeletedHandler
participant FormDB
participant FormGrpc
participant DeleteGuard
DataModeling->>Kafka: Publish ModelDeletedEvent
Kafka->>ModelDeletedHandler: Deliver ModelDeletedEvent
ModelDeletedHandler->>FormDB: Load forms and RelationPicker fields
ModelDeletedHandler->>FormDB: Create/Mark FormModelReference rows (IsBroken=true)
ModelDeletedHandler->>FormDB: SaveChangesAsync
DeleteGuard->>FormGrpc: CountActiveModelReferences(modelId, orgId)
FormGrpc-->>DeleteGuard: ActiveReferenceCount
alt ActiveReferenceCount > 0
DeleteGuard-->>DataModeling: Return Conflict failure
else
DeleteGuard-->>DataModeling: Return Success
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
…ests Register IFormModelReferenceRepository and IFormModelReferenceSync in DI so AddFieldToFormHandler resolves in API tests. Refactor ModelDeletedHandler to use injected DbContext (test schema). Block DELETE model when active form references exist (409). Co-authored-by: Phuong Nguyen <phuongnse@users.noreply.github.com>
…signer Manual migration lacked .Designer.cs so EF never applied it in tests/CI. Regenerated via dotnet ef migrations add; table form_model_references is now in the migration chain. Co-authored-by: Phuong Nguyen <phuongnse@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/Axis.Api/Endpoints/ModelEndpoints.cs (1)
71-79:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winDeclare the new 409 response in endpoint metadata.
The route can now return conflict, but OpenAPI metadata for
MapDeletedoes not include.ProducesProblem(409).Suggested patch
group.MapDelete("/{modelId:guid}", DeleteModel) .RequireAuthorization(Permissions.DataModeling.ModelDelete) .WithName("DeleteModel") .WithSummary("Soft-delete a data model") .WithTags("DataModeling") .Produces(204) .ProducesProblem(401) .ProducesProblem(403) + .ProducesProblem(409) .ProducesProblem(404);🤖 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/Axis.Api/Endpoints/ModelEndpoints.cs` around lines 71 - 79, The OpenAPI metadata for the DeleteModel endpoint is missing the 409 conflict response; update the MapDelete("/{modelId:guid}", DeleteModel) endpoint fluent chain to include a .ProducesProblem(409) so the DeleteModel route reflects that it can return a 409 Conflict; ensure you add the call alongside the existing .ProducesProblem(401/.403/.404) in the same chain.
🤖 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/Axis.Api/Endpoints/ModelEndpoints.cs`:
- Line 4: The DeleteModel endpoint currently contains a deletion guard and a
direct dependency on Axis.FormBuilder.Application.Repositories; remove that
domain validation and the using directive from the endpoint and instead perform
the reference check inside the application command handler (e.g., the
DeleteModelCommand/handler) so the handler returns a failure Result when
references exist; from the endpoint, only call mediator.Send(deleteModelCommand)
and map the returned Result to an HTTP response with ToProblemDetails(),
ensuring no in-process calls into other modules from the endpoint.
In
`@tests/Modules/FormBuilder/Axis.FormBuilder.Application.Tests/Commands/AddFieldToFormHandlerTests.cs`:
- Around line 16-17: The tests create a mock IFormModelReferenceSync but never
assert it was invoked; update the success tests (e.g.,
Handle_WhenFormExists_AddsFieldAndReturnsFieldId) to verify the handler calls
the sync service after adding a field by asserting the mock
_formModelReferenceSync received the expected call (use
Received/ReceivedWithAnyArgs and match on the FormModelReference or formId
argument), ensuring the AddFieldToFormHandler triggers
IFormModelReferenceSync.Sync (or the actual method name) once after a successful
add; apply the same verification to the other similar test(s).
In
`@tests/Modules/FormBuilder/Axis.FormBuilder.Application.Tests/Commands/RemoveFieldFromFormHandlerTests.cs`:
- Around line 17-18: The test currently never verifies that
IFormModelReferenceSync is invoked; update the success test
Handle_WhenFieldExists_RemovesAndSaves in RemoveFieldFromFormHandlerTests to
assert that the mock IFormModelReferenceSync (the _formModelReferenceSync field)
was called once after handler.Handle(...) completes—use Substitute's Received(1)
on the appropriate method (the sync method that updates FormModelReference) with
the expected form id (or matching argument) to ensure the handler calls the sync
service after removing the field.
In
`@tests/Modules/FormBuilder/Axis.FormBuilder.Application.Tests/Queries/GetFormByIdHandlerTests.cs`:
- Around line 14-22: Add a test that verifies broken-field detection: configure
the mocked IFormModelReferenceRepository (_formModelReferenceRepo) to return a
HashSet containing the specific FormField.Id, arrange the repository (_repo) to
return a FormDefinition with that field, invoke GetFormByIdHandler.Handle via
the GetFormByIdQuery, and assert the returned FormDetailDto.Fields contains the
field and its IsBroken property is true (suggested test name:
Handle_WhenFieldIsBroken_MarksDtoAsBroken). Ensure you use existing
helpers/constructors (FormDefinition.Create, FormDefinition.AddField) and the
handler instance _handler created in the test fixture.
In
`@tests/Modules/FormBuilder/Axis.FormBuilder.Infrastructure.Tests/Handlers/ModelDeletedHandlerTests.cs`:
- Around line 37-65: The test currently saves a form with a RelationPicker field
directly to the DbContext, so no FormModelReference is created and
ModelDeletedHandler has nothing to mark; modify the test to create the
FormModelReference before invoking the handler by either (a) invoking the real
AddFieldToFormHandler (or the code path that uses IFormModelReferenceSync) to
add the field so the reference is created, or (b) directly create and save a
FormModelReference record for the form/targetModelId in the test DbContext prior
to calling CreateHandler(...).Handle(BuildEvent(targetModelId), ...), then
assert that the existing reference IsBroken is true.
---
Outside diff comments:
In `@src/Axis.Api/Endpoints/ModelEndpoints.cs`:
- Around line 71-79: The OpenAPI metadata for the DeleteModel endpoint is
missing the 409 conflict response; update the MapDelete("/{modelId:guid}",
DeleteModel) endpoint fluent chain to include a .ProducesProblem(409) so the
DeleteModel route reflects that it can return a 409 Conflict; ensure you add the
call alongside the existing .ProducesProblem(401/.403/.404) in the same chain.
🪄 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: 2718fe70-8a3f-4dc6-abcd-58c85e54d5e3
📒 Files selected for processing (25)
docs/PROGRESS.mddocs/epics/E03-data-modeling/features/F01-model-definition.mddocs/epics/E05-form-builder/README.mdsrc/Axis.Api/Endpoints/ModelEndpoints.cssrc/Modules/FormBuilder/Axis.FormBuilder.Application/Commands/AddFieldToForm/AddFieldToFormHandler.cssrc/Modules/FormBuilder/Axis.FormBuilder.Application/Commands/RemoveFieldFromForm/RemoveFieldFromFormHandler.cssrc/Modules/FormBuilder/Axis.FormBuilder.Application/Queries/GetFormById/FormFieldDto.cssrc/Modules/FormBuilder/Axis.FormBuilder.Application/Queries/GetFormById/GetFormByIdHandler.cssrc/Modules/FormBuilder/Axis.FormBuilder.Application/Repositories/IFormModelReferenceRepository.cssrc/Modules/FormBuilder/Axis.FormBuilder.Application/Services/IFormModelReferenceSync.cssrc/Modules/FormBuilder/Axis.FormBuilder.Domain/ReadModels/FormModelReference.cssrc/Modules/FormBuilder/Axis.FormBuilder.Infrastructure/Axis.FormBuilder.Infrastructure.csprojsrc/Modules/FormBuilder/Axis.FormBuilder.Infrastructure/Extensions/FormBuilderInfrastructureExtensions.cssrc/Modules/FormBuilder/Axis.FormBuilder.Infrastructure/Handlers/ModelDeletedHandler.cssrc/Modules/FormBuilder/Axis.FormBuilder.Infrastructure/Migrations/20260524150108_AddFormModelReferences.Designer.cssrc/Modules/FormBuilder/Axis.FormBuilder.Infrastructure/Migrations/20260524150108_AddFormModelReferences.cssrc/Modules/FormBuilder/Axis.FormBuilder.Infrastructure/Migrations/FormBuilderDbContextModelSnapshot.cssrc/Modules/FormBuilder/Axis.FormBuilder.Infrastructure/Persistence/Configurations/FormModelReferenceConfiguration.cssrc/Modules/FormBuilder/Axis.FormBuilder.Infrastructure/Persistence/FormBuilderDbContext.cssrc/Modules/FormBuilder/Axis.FormBuilder.Infrastructure/Repositories/FormModelReferenceRepository.cssrc/Modules/FormBuilder/Axis.FormBuilder.Infrastructure/Services/FormModelReferenceSync.cstests/Modules/FormBuilder/Axis.FormBuilder.Application.Tests/Commands/AddFieldToFormHandlerTests.cstests/Modules/FormBuilder/Axis.FormBuilder.Application.Tests/Commands/RemoveFieldFromFormHandlerTests.cstests/Modules/FormBuilder/Axis.FormBuilder.Application.Tests/Queries/GetFormByIdHandlerTests.cstests/Modules/FormBuilder/Axis.FormBuilder.Infrastructure.Tests/Handlers/ModelDeletedHandlerTests.cs
- Move US-033 form-reference check to DeleteModelHandler via IModelDeletionGuard - FormModelDeletionGuard in Axis.Api composition root (no FormBuilder ref in endpoint) - OpenAPI: ProducesProblem(409) on DELETE /models - Assert IFormModelReferenceSync in add/remove field handler tests - Add GetFormById broken-field test; extend ModelDeletedHandler tests Co-authored-by: Phuong Nguyen <phuongnse@users.noreply.github.com>
Co-authored-by: Phuong Nguyen <phuongnse@users.noreply.github.com>
There was a problem hiding this comment.
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 `@src/Axis.Api/Infrastructure/FormModelDeletionGuard.cs`:
- Around line 8-17: FormModelDeletionGuard is making an in-process call to
another module via IFormModelReferenceRepository
(CountActiveReferencesToModelAsync) inside ValidateCanDeleteAsync, which breaks
the module-boundary rule; replace this direct repository usage with a
cross-module integration: inject and call a transport-oriented client (e.g.,
IFormModelReferenceClient or IFormModelReferenceGrpcClient/KafkaPublisher) that
implements the contract for querying reference counts via gRPC/Kafka, update
ValidateCanDeleteAsync to call that client method instead of
CountActiveReferencesToModelAsync, and adjust DI registration to bind the new
transport client implementation so all cross-module communication goes through
the contract layer rather than in-process repository calls.
🪄 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: 9da405b0-4e2d-47f9-ae18-bd2446b5f624
📒 Files selected for processing (10)
src/Axis.Api/Endpoints/ModelEndpoints.cssrc/Axis.Api/Infrastructure/FormModelDeletionGuard.cssrc/Axis.Api/Program.cssrc/Modules/DataModeling/Axis.DataModeling.Application/Commands/DeleteModel/DeleteModelHandler.cssrc/Modules/DataModeling/Axis.DataModeling.Application/Services/IModelDeletionGuard.cstests/Modules/DataModeling/Axis.DataModeling.Application.Tests/Commands/DeleteModelHandlerTests.cstests/Modules/FormBuilder/Axis.FormBuilder.Application.Tests/Commands/AddFieldToFormHandlerTests.cstests/Modules/FormBuilder/Axis.FormBuilder.Application.Tests/Commands/RemoveFieldFromFormHandlerTests.cstests/Modules/FormBuilder/Axis.FormBuilder.Application.Tests/Queries/GetFormByIdHandlerTests.cstests/Modules/FormBuilder/Axis.FormBuilder.Infrastructure.Tests/Handlers/ModelDeletedHandlerTests.cs
✅ Files skipped from review due to trivial changes (1)
- src/Axis.Api/Endpoints/ModelEndpoints.cs
| internal sealed class FormModelDeletionGuard(IFormModelReferenceRepository formModelReferences) | ||
| : IModelDeletionGuard | ||
| { | ||
| public async Task<Result> ValidateCanDeleteAsync( | ||
| Guid modelId, | ||
| Guid organizationId, | ||
| CancellationToken cancellationToken = default) | ||
| { | ||
| int formReferenceCount = await formModelReferences.CountActiveReferencesToModelAsync( | ||
| modelId, organizationId, cancellationToken); |
There was a problem hiding this comment.
Cross-module in-process repository call violates the module boundary rule.
Line 16 invokes IFormModelReferenceRepository from another module’s Application layer directly. This creates synchronous in-process coupling; this check should cross module boundaries via Kafka/gRPC contracts instead of direct Application/Infrastructure calls.
As per coding guidelines, "No in-process method calls into another module's Application or Infrastructure layers; use Kafka events or gRPC calls for cross-module communication".
🤖 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/Axis.Api/Infrastructure/FormModelDeletionGuard.cs` around lines 8 - 17,
FormModelDeletionGuard is making an in-process call to another module via
IFormModelReferenceRepository (CountActiveReferencesToModelAsync) inside
ValidateCanDeleteAsync, which breaks the module-boundary rule; replace this
direct repository usage with a cross-module integration: inject and call a
transport-oriented client (e.g., IFormModelReferenceClient or
IFormModelReferenceGrpcClient/KafkaPublisher) that implements the contract for
querying reference counts via gRPC/Kafka, update ValidateCanDeleteAsync to call
that client method instead of CountActiveReferencesToModelAsync, and adjust DI
registration to bind the new transport client implementation so all cross-module
communication goes through the contract layer rather than in-process repository
calls.
Shared fixture schema caused IX_form_definitions_organization_id_name violation when both tests used the same form name "Intake". Co-authored-by: Phuong Nguyen <phuongnse@users.noreply.github.com>
Replace in-process FormModelDeletionGuard with FormBuilder gRPC contract. Move Identity repository usage out of Api endpoints into queries/handlers. Add GatewayBoundaryTests to catch future Axis.Api cross-module DI. Co-authored-by: Phuong Nguyen <phuongnse@users.noreply.github.com>
Co-authored-by: Phuong Nguyen <phuongnse@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
src/Modules/DataModeling/Axis.DataModeling.Infrastructure/Extensions/DataModelingInfrastructureExtensions.cs (1)
27-32: ⚡ Quick winFail fast when
Modules:FormBuilder:GrpcUrlis missing.Defaulting to
http://localhost:5280can hide misconfiguration and route calls to a non-existent/local endpoint outside dev environments.Suggested change
- string formBuilderGrpcUrl = configuration["Modules:FormBuilder:GrpcUrl"] - ?? "http://localhost:5280"; + string? formBuilderGrpcUrl = configuration["Modules:FormBuilder:GrpcUrl"]; + if (string.IsNullOrWhiteSpace(formBuilderGrpcUrl)) + { + throw new InvalidOperationException("Missing configuration 'Modules:FormBuilder:GrpcUrl'."); + } services.AddGrpcClient<FormModelReferenceService.FormModelReferenceServiceClient>(opts => { opts.Address = new Uri(formBuilderGrpcUrl); });🤖 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/DataModeling/Axis.DataModeling.Infrastructure/Extensions/DataModelingInfrastructureExtensions.cs` around lines 27 - 32, The code defaults Modules:FormBuilder:GrpcUrl to "http://localhost:5280", which can hide misconfiguration; update DataModelingInfrastructureExtensions to fail fast by validating the configuration key before calling services.AddGrpcClient: read configuration["Modules:FormBuilder:GrpcUrl"] into formBuilderGrpcUrl and if it's null or empty throw an InvalidOperationException (or ConfigurationException) with a clear message instructing to set Modules:FormBuilder:GrpcUrl (mentioning the configuration key), otherwise use the value when calling AddGrpcClient<FormModelReferenceService.FormModelReferenceServiceClient>.src/Modules/DataModeling/Axis.DataModeling.Infrastructure/Grpc/FormModelDeletionGuard.cs (1)
63-63: ⚡ Quick winUse lowercase gRPC metadata key for auth header
Grpc.Core.Metadata.Addnormalizes keys to lowercase, so"Authorization"won’t be rejected due to case alone; switching to"authorization"keeps consistency with gRPC metadata conventions and avoids potential interop edge cases.Proposed fix
- headers.Add("Authorization", authorization); + headers.Add("authorization", authorization);🤖 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/DataModeling/Axis.DataModeling.Infrastructure/Grpc/FormModelDeletionGuard.cs` at line 63, In FormModelDeletionGuard.cs change the gRPC metadata key to lowercase by updating the call that adds the header (headers.Add(...)) to use "authorization" instead of "Authorization"; locate the headers.Add invocation within the FormModelDeletionGuard class/method and replace the header key so it follows gRPC metadata conventions and avoids interop edge cases.
🤖 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.Contracts/Protos/axis/formbuilder/v1/form_model_reference_service.proto`:
- Line 5: Buf lint fails PACKAGE_DIRECTORY_MATCH because the proto package
declarations axis.formbuilder.v1 and axis.identity.v1 are not located under the
configured Buf root path; either update your buf.yaml or buf.work.yaml
build.roots (or module input roots) so the directory containing axis/ is the
root for the module, or move the proto files so their filesystem paths match
axis/formbuilder/v1 and axis/identity/v1; after adjusting build.roots or
relocating the files, re-run buf lint to confirm PACKAGE_DIRECTORY_MATCH errors
for axis.formbuilder.v1 and axis.identity.v1 are resolved.
In
`@src/Modules/Identity/Axis.Identity.Application/Commands/DeactivateUser/DeactivateUserHandler.cs`:
- Around line 26-33: The code currently falls back to Guid.Empty when
roleRepo.GetByNameAsync("Admin", ...) returns null, which can bypass the
last-admin protection; update DeactivateUserHandler to fail fast if adminRole is
null (throw or return a Result.Failure) instead of using Guid.Empty, so the
subsequent logic that calls CountAdminsAsync(command.OrganizationId,
adminRoleId, ...) and checks user.RoleIds.Contains(adminRoleId) only runs with a
valid adminRole.Id; reference adminRole, roleRepo.GetByNameAsync, adminRoleId,
userRepo.CountAdminsAsync, and user.RoleIds.Contains when making the change.
In
`@src/Modules/Identity/Axis.Identity.Application/Queries/GetUserTokenClaims/GetUserTokenClaimsHandler.cs`:
- Around line 25-27: Ensure GetUserTokenClaimsHandler validates organization
scope: do not let query.OrganizationId silently override user.OrganizationId.
Before calling roleRepo.GetByIdsAsync, check if query.OrganizationId is non-null
and differs from user.OrganizationId; if so, reject the request (throw an
appropriate authorization/validation exception or return an error) rather than
proceeding. Then set orgId to the validated value (user.OrganizationId or the
allowed query.OrganizationId) and only after that call roleRepo.GetByIdsAsync
with the validated orgId.
In `@tests/Architecture/Axis.Architecture.Tests/GatewayBoundaryTests.cs`:
- Around line 25-27: The filter that builds endpointTypes currently checks
t.Namespace == "Axis.Api.Endpoints" which excludes nested namespaces like
Axis.Api.Endpoints.Admin; update the predicate used on api!.GetTypes() (the
endpointTypes assignment) to include nested namespaces by checking t.Namespace
is not null and either equals "Axis.Api.Endpoints" or starts with
"Axis.Api.Endpoints." (e.g., use t.Namespace?.StartsWith("Axis.Api.Endpoints")
or an explicit equals OR starts-with check) so all Endpoint namespaces under
Axis.Api.Endpoints are included for validation.
---
Nitpick comments:
In
`@src/Modules/DataModeling/Axis.DataModeling.Infrastructure/Extensions/DataModelingInfrastructureExtensions.cs`:
- Around line 27-32: The code defaults Modules:FormBuilder:GrpcUrl to
"http://localhost:5280", which can hide misconfiguration; update
DataModelingInfrastructureExtensions to fail fast by validating the
configuration key before calling services.AddGrpcClient: read
configuration["Modules:FormBuilder:GrpcUrl"] into formBuilderGrpcUrl and if it's
null or empty throw an InvalidOperationException (or ConfigurationException)
with a clear message instructing to set Modules:FormBuilder:GrpcUrl (mentioning
the configuration key), otherwise use the value when calling
AddGrpcClient<FormModelReferenceService.FormModelReferenceServiceClient>.
In
`@src/Modules/DataModeling/Axis.DataModeling.Infrastructure/Grpc/FormModelDeletionGuard.cs`:
- Line 63: In FormModelDeletionGuard.cs change the gRPC metadata key to
lowercase by updating the call that adds the header (headers.Add(...)) to use
"authorization" instead of "Authorization"; locate the headers.Add invocation
within the FormModelDeletionGuard class/method and replace the header key so it
follows gRPC metadata conventions and avoids interop edge cases.
🪄 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: 6663a51f-fb46-4903-aa34-6e37ec0e41f6
📒 Files selected for processing (26)
Directory.Packages.propssrc/Axis.Api/Endpoints/ConnectEndpoints.cssrc/Axis.Api/Endpoints/InvitationEndpoints.cssrc/Axis.Api/Endpoints/UserEndpoints.cssrc/Axis.Api/Program.cssrc/Axis.Api/appsettings.jsonsrc/Modules/DataModeling/Axis.DataModeling.Application/Services/IModelDeletionGuard.cssrc/Modules/DataModeling/Axis.DataModeling.Infrastructure/Axis.DataModeling.Infrastructure.csprojsrc/Modules/DataModeling/Axis.DataModeling.Infrastructure/Extensions/DataModelingInfrastructureExtensions.cssrc/Modules/DataModeling/Axis.DataModeling.Infrastructure/Grpc/FormModelDeletionGuard.cssrc/Modules/FormBuilder/Axis.FormBuilder.Contracts/Axis.FormBuilder.Contracts.csprojsrc/Modules/FormBuilder/Axis.FormBuilder.Contracts/Protos/axis/formbuilder/v1/form_model_reference_service.protosrc/Modules/FormBuilder/Axis.FormBuilder.Infrastructure/Axis.FormBuilder.Infrastructure.csprojsrc/Modules/FormBuilder/Axis.FormBuilder.Infrastructure/Extensions/FormBuilderInfrastructureExtensions.cssrc/Modules/FormBuilder/Axis.FormBuilder.Infrastructure/Grpc/FormModelReferenceGrpcService.cssrc/Modules/Identity/Axis.Identity.Application/Commands/DeactivateUser/DeactivateUserCommand.cssrc/Modules/Identity/Axis.Identity.Application/Commands/DeactivateUser/DeactivateUserHandler.cssrc/Modules/Identity/Axis.Identity.Application/Queries/GetCurrentUserProfile/GetCurrentUserProfileHandler.cssrc/Modules/Identity/Axis.Identity.Application/Queries/GetCurrentUserProfile/GetCurrentUserProfileQuery.cssrc/Modules/Identity/Axis.Identity.Application/Queries/GetInvitationByToken/GetInvitationByTokenHandler.cssrc/Modules/Identity/Axis.Identity.Application/Queries/GetInvitationByToken/GetInvitationByTokenQuery.cssrc/Modules/Identity/Axis.Identity.Application/Queries/GetUserTokenClaims/GetUserTokenClaimsHandler.cssrc/Modules/Identity/Axis.Identity.Application/Queries/GetUserTokenClaims/GetUserTokenClaimsQuery.cstests/Architecture/Axis.Architecture.Tests/GatewayBoundaryTests.cstests/Architecture/Axis.Architecture.Tests/README.mdtests/Modules/Identity/Axis.Identity.Application.Tests/Commands/DeactivateUserHandlerTests.cs
✅ Files skipped from review due to trivial changes (2)
- src/Modules/DataModeling/Axis.DataModeling.Infrastructure/Axis.DataModeling.Infrastructure.csproj
- Directory.Packages.props
|
|
||
| option csharp_namespace = "Axis.FormBuilder.Contracts.Grpc"; | ||
|
|
||
| package axis.formbuilder.v1; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify Buf configuration roots and reproduce the lint error locally.
fd -i -a 'buf.yaml'
fd -i -a 'buf.work.yaml'
fd -a 'form_model_reference_service.proto'
if command -v buf >/dev/null 2>&1; then
buf lint
fiRepository: phuong-labs/axis
Length of output: 812
🏁 Script executed:
#!/bin/bash
set -euo pipefail
fd -i 'buf.yaml' || true
fd -i 'buf.work.yaml' || true
fd -i 'form_model_reference_service.proto' || true
command -v buf >/dev/null 2>&1 && buf lintRepository: phuong-labs/axis
Length of output: 793
🏁
Length of output: 25
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show Buf roots / inputs that determine the "relative to root" directory used by PACKAGE_DIRECTORY_MATCH
fd -i 'buf.yaml' -a 2>/dev/null || true
fd -i 'buf.work.yaml' -a 2>/dev/null || true
for f in $(fd -i 'buf.yaml' -a 2>/dev/null || true) $(fd -i 'buf.work.yaml' -a 2>/dev/null || true); do
echo "---- $f ----"
rg -n '^(.*(build|roots|workspace|modules|breaking|lint|deps|input).*)$' "$f" || true
done
# Re-run lint to ensure the fix addresses the same error
command -v buf >/dev/null 2>&1 && buf lintRepository: phuong-labs/axis
Length of output: 680
🏁 Script executed:
#!/bin/bash
set -euo pipefail
fd -i 'buf.yaml' -a --type f || true
fd -i 'buf.work.yaml' -a --type f || true
for f in $(fd -i 'buf.yaml' --type f 2>/dev/null || true) $(fd -i 'buf.work.yaml' --type f 2>/dev/null || true); do
echo "---- $f ----"
rg -n '(build\.roots|build\.root|modules|workspace|lint|breaking|deps|input[s]?:|roots:)' "$f" || true
done
command -v buf >/dev/null 2>&1 && buf lint || trueRepository: phuong-labs/axis
Length of output: 678
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate any buf config files (buf.yaml/buf.work.yaml or others matching buf*.yml)
mapfile -t cfgs < <(fd -i -g 'buf*.y*ml' --type f .)
for f in "${cfgs[@]}"; do
echo "---- $f ----"
rg -n '(build\.roots|build\.root|roots:|input[s]?:|workspace:|modules:)' "$f" || true
done
command -v buf >/dev/null 2>&1 && buf lintRepository: phuong-labs/axis
Length of output: 117
Fix Buf PACKAGE_DIRECTORY_MATCH for axis.formbuilder.v1 (also affects axis.identity.v1).
buf lint fails because package "axis.formbuilder.v1" must be in axis/formbuilder/v1 relative to the configured Buf root, but the file is under src/Modules/FormBuilder/Axis.FormBuilder.Contracts/Protos/axis/formbuilder/v1/ (and similarly axis.identity.v1 is under src/Modules/Identity/Axis.Identity.Contracts/Protos/axis/identity/v1/). Update buf.yaml/buf.work.yaml build.roots / module input roots so axis/... is at the expected level, or move the proto files to match axis/formbuilder/v1 and axis/identity/v1.
- Check
buf.yaml/buf.work.yamlforbuild.roots(and related input/module root settings) and align the root soaxis/formbuilder/v1andaxis/identity/v1are directly under it. - Re-run
buf lintand ensure thePACKAGE_DIRECTORY_MATCHerrors are gone.
[/request_verification]
🧰 Tools
🪛 Buf (1.69.0)
[error] 5-5: Files with package "axis.formbuilder.v1" must be within a directory "axis/formbuilder/v1" relative to root but were in directory "src/Modules/FormBuilder/Axis.FormBuilder.Contracts/Protos/axis/formbuilder/v1".
(PACKAGE_DIRECTORY_MATCH)
🤖 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/FormBuilder/Axis.FormBuilder.Contracts/Protos/axis/formbuilder/v1/form_model_reference_service.proto`
at line 5, Buf lint fails PACKAGE_DIRECTORY_MATCH because the proto package
declarations axis.formbuilder.v1 and axis.identity.v1 are not located under the
configured Buf root path; either update your buf.yaml or buf.work.yaml
build.roots (or module input roots) so the directory containing axis/ is the
root for the module, or move the proto files so their filesystem paths match
axis/formbuilder/v1 and axis/identity/v1; after adjusting build.roots or
relocating the files, re-run buf lint to confirm PACKAGE_DIRECTORY_MATCH errors
for axis.formbuilder.v1 and axis.identity.v1 are resolved.
Co-authored-by: Phuong Nguyen <phuongnse@users.noreply.github.com>
Co-authored-by: Phuong Nguyen <phuongnse@users.noreply.github.com>
Summary
FormBuilder consumes
ModelDeletedEventfor broken Relation Picker refs (US-033 / E05), withform_model_referencesread model, sync on field changes,IsBrokenon GET form, and 409 when active form references block model delete.This revision addresses PR review module-boundary feedback: deletion guard no longer calls FormBuilder Application repositories from
Axis.Api. DataModeling validates via FormBuilder gRPC (CountActiveModelReferences). Gateway endpoints no longer inject Identity repositories (MediatR queries/handlers instead).GatewayBoundaryTestsenforcesAxis.Apicannot depend on other modules' Application layers.Linked spec
Requirements
ModelDeletedHandlerconsumesModelDeletedEvent(Contracts-only ref to DataModeling)FormFieldDto.IsBrokenexposed on GET formAddFormModelReferencesAxis.Api(gRPC + gateway fitness tests)Deferred: WorkflowBuilder
record.*trigger broken refs; DataModeling relation-field broken flags on other models; frontend broken indicators.Summary by CodeRabbit
New Features
Bug Fixes
Documentation