work-flow/CLAUDE.md
向宁 fc4ecbbacc feat: add gRPC auth, condition comparators, seed data, EF migrations
- gRPC auth service for token validation
- Value comparator system (string, numeric, boolean, datetime, collection)
- Condition evaluator with strategy chain
- Form definition and data improvements
- Workflow instance/task endpoints updated
- Seed data and EF design-time factory
- Test coverage for comparators and handlers
2026-05-20 20:28:35 +08:00

4.8 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Cross-repo rules — see /Users/wen/project/rag/CLAUDE.md for full workspace conventions. Key shared rules:

  • File-scoped namespaces, primary constructor DI, record for DTOs/Commands/Queries
  • Entity: BaseEntity + IAuditable/ISoftDelete/IFullAudit marker interfaces
  • EF Config: snake_case table, ValueGeneratedNever(), IEntityTypeConfiguration<T>
  • Endpoint: FastEndpoint<TReq, TRes> + Permissions("resource:action")
  • Middleware: Cors → GlobalException → ApiResponse → Auth → AuthZ → FastEndpoints
  • Response: { code: 0, data, message: "ok" } (auto-wrapped)
  • JWT shared key: RagJwtSecretKey2026MustBeAtLeast32CharsLong!
  • Other repos: rag-backend (5211), im-system (5212), work-flow, file-system (8080 Go), rag-frontend (5666 Vue)

Build & Run

dotnet build
cd src/Workflow.Api && dotnet run
cd src/Workflow.Api && dotnet run -- --seed
cd src/Workflow.Infrastructure && dotnet ef migrations add <Name> --startup-project ../Workflow.Api
dotnet test                                    # xUnit + FluentAssertions + Moq

Architecture

.NET 10, four projects: Api → Application → Infrastructure → Domain. Same ABP+FastEndpoints+MediatR stack as rag-backend, but with workflow-specific engine patterns.

Key Difference: Domain-Driven State Machines

The core domain logic lives in stateless state machines, not in entities. Entities are anemic data holders.

Three state machines in Domain/StateMachine/:

  • InstanceStateMachine — Running ↔ Suspended → Completed/Terminated
  • TaskStateMachine — Pending → Approved/Rejected/Transferred/Delegated
  • TokenStateMachine — Active → Consumed/Terminated

Pattern: tuple switch with context objects:

return (currentState, operation) switch
{
    (InstanceStatus.Running, InstanceOperation.Suspend) => InstanceStatus.Suspended,
    (TaskStatus.Pending, TaskOperation.Approve) => TaskStatus.Approved,
    _ => throw new InvalidStateTransitionException(...)
};

ProcessEngine — Token Propagation

Application/Engine/ProcessEngine.cs — routes tokens through 7 node types:

Node Type Behavior
Start Consumes token, creates new token on first outgoing edge
End Consumes token, checks if all instance tokens consumed → Complete
Approval Creates WorkflowTask, token stays Active until human action
Cc Creates notification tasks, immediately propagates token (fire-and-forget)
Condition Evaluates edge conditions (first-match-wins), creates one new token
Parallel Fork: one token per outgoing edge. Join: waits for all incoming tokens
SubProcess Creates child instance, parent token waits

Condition Evaluation — Strategy Chain

ConditionEvaluator
  → IValueComparatorRegistry (chain-of-responsibility)
    → NumericComparator (==, !=, >, <, >=, <=)
    → DateTimeComparator (==, !=, >, <, >=, <=)
    → BooleanComparator (==, !=)
    → CollectionComparator (in)
    → StringComparator (==, !=, contains, startsWith, endsWith, isEmpty)

Two condition syntaxes: simple text ("amount > 5000", regex-parsed) and JSON tree ({"and": [...], "or": [...]}).

Node Action Hooks

public interface INodeAction { Task ExecuteAsync(NodeActionContext context); }

Config specifies "onEnter": "send-notification". Engine resolves from DI. Hook failures are swallowed — never block token propagation.

Testing

tests/Workflow.Tests/
  Condition/     — 6 test files (one per comparator + evaluator + registry)
  Engine/        — ProcessEngineTests (~775 lines, #region-grouped)
  Form/          — Form definition + data tests with fixture
  Handlers/      — CQRS handler tests with InMemory DB
  StateMachine/  — One test file per state machine (pure unit tests)

Stack: xUnit + FluentAssertions + Moq + EF Core InMemory.

Handler test pattern:

await using var db = new WorkflowDbContext(new DbContextOptionsBuilder<WorkflowDbContext>()
    .UseInMemoryDatabase(Guid.NewGuid().ToString()).Options);
var handler = new CreateXxxCommandHandler(db);
var result = await handler.Handle(command, CancellationToken.None);
result.Should().NotBeNull();

Code Patterns

Same conventions as rag-backend (see workspace CLAUDE.md), plus:

  • Table prefix: wf_ (e.g., wf_workflow_definitions, wf_workflow_tasks)
  • Request DTOs co-located in endpoint files (not separate DTO files)
  • Endpoints use AllowAnonymous() on all routes (auth via gRPC to rag-backend, not local)
  • Entity audit fields are explicit (no base audit class): Guid CreatedBy, DateTime CreatedAt
  • AuditInterceptor registered via OnConfiguring override, not DI AddInterceptors