Agentic Guardrails and Anti-Drift Standards
Scaffolding sequence, XML rule tags, anti-drift patterns, and verification pipelines. Forbidden packages: forbidden-packages.md. Writing rules: writing-style.md.
Every automated agent MUST read AGENTS.md first, then this file when implementing features or running verification.
1. Rule Index (XML)
AI agents MUST parse and apply these <Rule> tags.
2. Deterministic Scaffolding Sequence
When implementing a new aggregate or use case, developers and AI agents MUST follow this exact sequence.
Step 0 (prerequisite): The Implementation Prerequisite Set MUST exist: docs/domain/{feature}/README.md, docs/domain/{feature}/{use-case}.md, and {use-case}.tests.md with a complete Test Coverage table for planned scenarios. If missing, write them per docs/guides/write-use-case-doc.md before any code. The test spec is the completion contract for tests; the Feature Spec is domain input for steps 1 through 3.
Run verification checkpoints after steps 3, 5, 5b, 7, and 8. Do not skip steps or write outer layers before completing inner boundaries.
graph TD Step0["0. Operation + test spec docs"] Step1["1. Domain Entity and ID"] Step2["2. Repository Interface"] Step3["3. EF Core Configuration"] Step3b["Checkpoint: dotnet build + dotnet ef migrations add"] Step4["4. Contracts Projects"] Step5["5. Handlers and Validators"] Step5b["5b. Update test spec + checkpoint tests"] Step5c["Checkpoint: dotnet build + dotnet test Application.Tests"] Step6["6. Reactions (conditional)"] Step7["7. WebApi Endpoint and DI"] Step7b["Checkpoint: dotnet build + dotnet test Integration.Tests"] Step8["8. Frontend feature use case (if applicable)"] Step8b["Checkpoint: pnpm lint + type-check + test"]
Step0 --> Step1 Step1 --> Step2 Step2 --> Step3 Step3 --> Step3b Step3b --> Step4 Step4 --> Step5 Step5 --> Step5b Step5b --> Step5c Step5c --> Step6 Step6 --> Step7 Step7 --> Step7b Step7b --> Step8 Step8 --> Step8b- Domain: Strongly-typed ID and aggregate root.
- Repository interface in
Domain. - EF configuration in
Infrastructure. Checkpoint:dotnet build apps/api/{ProjectName}.slnxanddotnet ef migrations add {Name}when schema changed. - Command/query records in Contracts projects.
- Handlers and validators in
Application.Write/Application.Read. 5b. Test spec checkpoint: Update{use-case}.tests.mdTest Coverage rows for scenarios implemented in step 5 (validators, handler orchestration). Add rows before writing Domain or Application tests discovered during implementation. Checkpoint:dotnet test apps/api/tests/{ProjectName}.Application.Testsand Domain tests when applicable. - Narrow interface in
Application.Reactions+ Infrastructure implementation (conditional: add only when an aggregate method raises a domain event that requires an external side effect). IEndpointand DI inWebApi/Infrastructure. Checkpoint:dotnet test apps/api/tests/{ProjectName}.Integration.Testsand architecture tests.- Frontend feature use case under
features/{feature}/{use-case}/(when the use case has UI). Checkpoint:pnpm lint && pnpm type-check && pnpm test.
Minimum commands between checkpoints:
dotnet build apps/api/{ProjectName}.slnx --configuration Releasedotnet test apps/api/{ProjectName}.slnx --configuration Release --no-builddotnet ef migrations add {MigrationName} \ --project apps/api/src/{ProjectName}.Infrastructure \ --startup-project apps/api/src/{ProjectName}.WebApi3. DO / DON’T Guardrails
Repository save boundary
// DO: stage write; pipeline persistspublic async Task AddAsync(Post post, CancellationToken cancellationToken){ await _dbContext.Posts.AddAsync(post, cancellationToken);}// DON'T: SaveChangesAsync in repositoryawait _dbContext.SaveChangesAsync(cancellationToken); // FORBIDDENAnti-drift: no stubs
// DO: implement behavior or omit until spec existsexport async function publishPost(postId: PostId) { const client = await getApiClient() const { error } = await client.POST("/posts/{id}/publish", { params: { path: { id: postId } } }) if (error) throw new Error("Publish failed")}// DON'T: placeholder stubexport async function publishPost(_postId: PostId) { // TODO: implement later throw new Error("Not implemented")}Anti-drift: Tailwind and shadcn bootstrap
Each Next.js app MUST have:
postcss.config.mjswith@tailwindcss/postcssapp/globals.csswith@import "tailwindcss"in the app entry file (not only via a package re-export; shadcn CLI validates the app file)@sourcedirectives coveringapp/,components/, andfeatures/npx shadcn@latest initcompleted (components.json,lib/utils.ts)
Shared theme tokens MAY live in packages/{name}-config-tailwind/theme.css. Each app imports tokens and owns its own @source scan roots.
// DO: theme token<div className="p-4 text-foreground bg-background" />// DON'T: arbitrary spacing/color<div className="p-[13px] text-[#3a3f51]" />/* DO: app/globals.css (Tailwind v4 + monorepo) */@import "tailwindcss";@import "tw-animate-css";@import "@project/config-tailwind/theme.css";
@source "../app/**/*.{js,ts,jsx,tsx}";@source "../components/**/*.{js,ts,jsx,tsx}";@source "../features/**/*.{js,ts,jsx,tsx}";Anti-drift: aggregate state persistence
// DO: TPH columns + interceptor; State stays on the aggregate in Domainbuilder.Ignore(p => p.State);builder.Property<string>(PostStateColumns.StateType).HasColumnName("state_type");// DON'T: parallel PublishedAt on Post aggregate rootpublic DateTimeOffset? PublishedAt { get; private set; }public PostState State => PublishedAt.HasValue ? new PublishedPostState(PublishedAt.Value) : new DraftPostState();
// DON'T: jsonb blob when project standards require TPH columnsbuilder.Property(p => p.State).HasColumnType("jsonb").HasConversion(...);
// DON'T: RehydrateState on the aggregateinternal void RehydrateState(string stateType, DateTimeOffset? publishedAt) { ... }After step 3 (EF configuration), verify dotnet ef migrations add produces state_type, published_at, and archived_at columns on the aggregate table, not a single jsonb state column. Copy from docs/blueprints/backend/post-state-tph.md.
3. LiteBus Module Registration
LiteBus registration is authoritative in docs/blueprints/backend/program-cs.md only. Other documents MUST reference that file instead of duplicating registration blocks.
// DON'T: calling AddCommandModule twice causes a duplicate key errorbuilder.Services.AddLiteBus(liteBus =>{ liteBus.AddCommandModule(module => { module.RegisterFromAssembly(typeof(ApplicationWriteAssemblyMarker).Assembly); });
liteBus.AddCommandModule(module => // FORBIDDEN — duplicate module call { module.RegisterFromAssembly(typeof(InfrastructureAssemblyMarker).Assembly); });});4. Domain Event Framework Coupling
// DO: domain event is a plain record with no framework dependencysealed record PostPublished(PostId PostId) : IDomainEvent;
// IDomainEvent is a project-defined marker — no base interface required:interface IDomainEvent;// DON'T: coupling domain events to a framework interfacesealed record PostPublished(PostId PostId) : IEvent; // FORBIDDEN — IEvent is a LiteBus interfacesealed record PostPublished(PostId PostId) : IDomainEvent, IEvent; // FORBIDDEN — same problem4. Mandatory Verification Pipeline
Before marking any task complete, an agent MUST complete docs/guides/definition-of-done.md and run every gate in docs/conventions/shared/ci.md that applies to the change.
Minimum commands:
dotnet build apps/api/{ProjectName}.slnx --configuration Releasedotnet test apps/api/{ProjectName}.slnx --configuration Release --no-buildpnpm install --frozen-lockfilepnpm lintpnpm type-checkpnpm testpnpm buildpnpm exec playwright test --config apps/web/playwright.config.tsSkip frontend steps when the project has no apps/web/.
Self-Correction Checklist
- Completed
docs/guides/definition-of-done.md. - No forbidden packages (
docs/conventions/shared/forbidden-packages.md). -
snake_caseon all PostgreSQL mappings. -
cancellationTokennaming on all async methods. -
.AsNoTracking()on read queries. - No cross-feature imports when frontend changed.
- OpenAPI artifacts committed when API contract changed.