Skip to content

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.

All aggregate mutations MUST occur through public business methods on the aggregate root. Setting properties directly from handlers is forbidden. Query handlers MUST inject IDatabaseContext and write direct LINQ projections. They MUST NOT load full aggregate roots or reference domain repository interfaces. Command handlers and repositories MUST NOT invoke SaveChangesAsync or commit transactions. The SaveChangesCommandPostHandler pipeline commits. Committed code MUST NOT contain TODO, FIXME, NotImplementedException stubs, or placeholder comments that defer required behavior. A single source file MUST NOT exceed 300 lines. Split into composed modules when approaching the limit. features/{a}/ MUST NOT import from features/{b}/. Promote shared code to @/shared/ or components/ui/. User-visible strings MUST come from API fields, next-intl keys, or the UI page doc / operation doc UI notes. Agents MUST NOT invent product or policy text. Agents MUST add or update a row in docs/domain/{feature}/{use-case}.tests.md Test Coverage table for every new or changed test (Layer, Class, Method, Variations). MUST NOT write a test for a scenario without a row; add the row first. Styling MUST use @theme tokens and standard scales. Arbitrary values (p-[13px], custom hex in className) are forbidden unless a project ADR documents an exception. Frontend HTTP calls MUST use getApiClient() with openapi-typescript paths. Raw fetch to ad-hoc URLs is forbidden except in Playwright mocks. Solutions following these standards MUST include {ProjectName}.Architecture.Tests with NetArchTest rules from `docs/decisions/architecture-tests-as-enforcement.md`. In monorepos, the .NET solution MUST live under apps/api/, not at the repository root src/. All runnable apps MUST live under apps/. A repo MAY contain multiple frontends and multiple .NET deployables. See `docs/conventions/shared/monorepo-structure.md`. When two normative standards files conflict and no project doc declares an override, stop. Quote both rules and ask for a human decision. Do not invent a compromise. When project docs (docs/domain/, app READMEs, project ADRs) overlap these standards, the project document wins. Read standards first, then project docs, then follow the most specific applicable guidance. The default frontend UI stack is shadcn/ui (CLI v4) with Tailwind v4. Each app owns components/ui/. Shared workspace packages MAY export theme CSS tokens only, not React components. A project ADR MAY document a different UI stack for a specific app.

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
  1. Domain: Strongly-typed ID and aggregate root.
  2. Repository interface in Domain.
  3. EF configuration in Infrastructure. Checkpoint: dotnet build apps/api/{ProjectName}.slnx and dotnet ef migrations add {Name} when schema changed.
  4. Command/query records in Contracts projects.
  5. Handlers and validators in Application.Write / Application.Read. 5b. Test spec checkpoint: Update {use-case}.tests.md Test 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.Tests and Domain tests when applicable.
  6. Narrow interface in Application.Reactions + Infrastructure implementation (conditional: add only when an aggregate method raises a domain event that requires an external side effect).
  7. IEndpoint and DI in WebApi / Infrastructure. Checkpoint: dotnet test apps/api/tests/{ProjectName}.Integration.Tests and architecture tests.
  8. 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:

Terminal window
dotnet build apps/api/{ProjectName}.slnx --configuration Release
dotnet test apps/api/{ProjectName}.slnx --configuration Release --no-build
dotnet ef migrations add {MigrationName} \
--project apps/api/src/{ProjectName}.Infrastructure \
--startup-project apps/api/src/{ProjectName}.WebApi

3. DO / DON’T Guardrails

Repository save boundary

// DO: stage write; pipeline persists
public async Task AddAsync(Post post, CancellationToken cancellationToken)
{
await _dbContext.Posts.AddAsync(post, cancellationToken);
}
// DON'T: SaveChangesAsync in repository
await _dbContext.SaveChangesAsync(cancellationToken); // FORBIDDEN

Anti-drift: no stubs

// DO: implement behavior or omit until spec exists
export 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 stub
export 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.mjs with @tailwindcss/postcss
  • app/globals.css with @import "tailwindcss" in the app entry file (not only via a package re-export; shadcn CLI validates the app file)
  • @source directives covering app/, components/, and features/
  • npx shadcn@latest init completed (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 Domain
builder.Ignore(p => p.State);
builder.Property<string>(PostStateColumns.StateType).HasColumnName("state_type");
// DON'T: parallel PublishedAt on Post aggregate root
public 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 columns
builder.Property(p => p.State).HasColumnType("jsonb").HasConversion(...);
// DON'T: RehydrateState on the aggregate
internal 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 error
builder.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 dependency
sealed 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 interface
sealed record PostPublished(PostId PostId) : IEvent; // FORBIDDEN — IEvent is a LiteBus interface
sealed record PostPublished(PostId PostId) : IDomainEvent, IEvent; // FORBIDDEN — same problem

4. 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:

Terminal window
dotnet build apps/api/{ProjectName}.slnx --configuration Release
dotnet test apps/api/{ProjectName}.slnx --configuration Release --no-build
pnpm install --frozen-lockfile
pnpm lint
pnpm type-check
pnpm test
pnpm build
pnpm exec playwright test --config apps/web/playwright.config.ts

Skip 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_case on all PostgreSQL mappings.
  • cancellationToken naming on all async methods.
  • .AsNoTracking() on read queries.
  • No cross-feature imports when frontend changed.
  • OpenAPI artifacts committed when API contract changed.