timewarpengineering/timewarp-architecture/tw-web-api-contracts

Claude Code and agent skill by timewarpengineering/timewarp-architecture

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

**TIMEWARP SKILL** — endpoint-centric Web.Contracts API contracts (Command, Query, ApiRoute, Operation, I*Details, Validator, serialization tests), one contract bound to HTTP, CLI, and MCP. Invoke before scaffolding or fixing contracts. WHEN: Add a CreateTodoItem command contract, Scaffold a GetRole query with ApiRoute and IRoleDetails for the edit form, Add a serialization round-trip test for my Command.

Install

npx vskill@latest install timewarpengineering/timewarp-architecture/tw-web-api-contracts

SKILL.md

Web API Contracts

Endpoint-centric, JSON-over-HTTP contracts designed for Blazor front ends. Each endpoint owns its request/response types. Mutability signals purpose: immutable members are read-only display data; mutable members on I*Details interfaces bind in EditForm without a separate view model. Shared validation lives on interfaces and is composed into per-endpoint validators.

This pattern appears across TimeWarp-based solutions. Project names vary (web-contracts, Web.Contracts, Api.Contracts, …) but the contract shape is the same.

Why endpoint-centric, not entity-centric: each operation owns its request/response types so endpoints do not couple through a shared entity DTO (over-fetch, under-fetch, and cross-endpoint breakage). Share structure and validation through I*Details interfaces composed into per-endpoint validators — that is what lets Blazor bind EditForm to one shape without a parallel view model.

Why generate FastEndpoints on both hosts: route, verb, and auth live on the contract, so there are no hand-written MVC BaseEndpoint shims. Validation stays on the mediator's FluentValidationBehavior. Hosts set EnableApiEndpointGeneration plus an ApiEndpointContractAssemblies AssemblyName allow-list (TWE008).

Browser-protocol endpoints (the exception)

A hand-written EndpointWithoutRequest at features/<slice>/…-endpoint-server.cs is allowed when the HTTP response is not the contract's JSON: an auth challenge, a form POST answered with a redirect, or an antiforgery token fetch. The living cases are ChallengeEntraEndpoint, SignOutBrowserSessionEndpoint, and SignOutAntiforgeryTokenEndpoint.

  • The path lives on a shared contracts class.
  • Any session or state change dispatches the existing mediator handler. Do not write a second

implementation.

  • The Design region says why the generator does not fit.
  • These bodies share nothing but the route and AllowAnonymous, so a generator is not worth it.
  • Validation stays on FluentValidationBehavior. Do not adopt FastEndpoints' validator integration.

Ordinary JSON operations stay generated. The filename function endpoint is for this exception (tw-feature-placement).

Detection — find the pattern in the current repo

Activate when any signal matches:

SignalHow to find it
Contracts project*.csproj named *contracts* (any casing); product contracts may live in a cohesive web/features/ tree (axis-1) or a project-local features//Features/ tree
Contract file layout**/features/**/<use-case>/*-contracts.cs (one folder per operation, contract beside its handler) or a shared *-contracts.cs at slice root for a file used by more than one operation (axis-1 grammar; also match legacy *.cs without the layer suffix). Search case-insensitively — repos use kebab features/ or Pascal Features/
Contract shellpublic static partial class + nested Query/Command + [ApiRoute(...)]
TimeWarp.Mediator returnIQuery<…> / ICommand<…> / IIdempotentCommand<…> over OneOf<Response, SharedProblemDetails> (bare IRequest<…> where the kind is unclear)
Neutral operation[Operation("…")] (TimeWarp.Contracts) on the nested Query/Command
Shared validationI*Details interface + AbstractValidator<I*Details>

The mediator is TimeWarp.Mediator (not MediatR). IQuery<T>, ICommand<T>, and IIdempotentCommand<T> all extend IRequest<T>, so handlers stay IRequestHandler<TRequest, TResponse>.

Before adding a contract, read 2–3 existing contracts in the same repo to match namespace root, folder casing, test project layout, and mock-service registration.

Folder and namespace rules

ConcernRuleExample
Feature folderPlural, domain-oriented; axis-1 cohesive root is web/features/<slice>/web/features/admin/roles/
Filename grammar<name>[-<function>]-<layer>.cs; contracts drop functioncreate-role/create-role-contracts.cs
NamespacePlural (does not track folder moves){Root}.Features.Admin.Roles
Use-case folderOne folder per operation, contract beside its handler (see tw-feature-placement)create-role/create-role-contracts.cs, get-role/get-role-contracts.cs
Shared bindable shapeSeparate file at slice root, not inside any use-case folderrole-details-contracts.cs (IRoleDetails)
RegistryFunction→layer SSOT (analyzer + membership guard)feature-filename-grammar.json — edit ⇒ full rebuild

Casing: kebab-case paths are canonical; if the repo already uses PascalCase folders (Features/Admin/Roles/), match it. Never mix casings within one repo.

The contract attributes (source-generated)

Two layers of attributes. Route/request attributes are emitted as public types in TimeWarp.Foundation.Features by the bundled contracts generator (class must be `partial`). Server-generation attributes live in TimeWarp.Architecture.Attributes and mark which contracts become hosted FastEndpoints.

Route / request attributes (on nested Query/Command)
AttributeGeneratesUse when
[ApiRoute("api/…", HttpVerb.X)]RouteTemplate const, GetRoute(), GetHttpVerb(), and a typed property per route parameter ({RoleId:guid} → Guid RoleId)Every contract request
[AuthApiRequest]Guid UserId { get; set; }; private GetAuthQueryParameters() only when the same type is IQueryStringRouteProvider or also has [OpenDataQueryParameters]List/GET queries that carry user identity in the query string
[OpenDataQueryParameters]Top/Skip/Filter/OrderBy/ReturnTotalCount + private GetOpenDataQueryParameters()Pageable/sortable list queries

The FastEndpoint generator matches TimeWarp.Foundation.Features.ApiRouteAttribute by fully qualified metadata name; the contracts generator emits that public type, so it ignores the generated app's RootNamespace.

[ApiRoute] parameter constraint grammar

Tokens are {Name} or {Name:constraint}; a type starts only after a colon, so bare {Date}/{LocationId} stay full identifiers typed string. guid → Guid, datetime → DateTime, min(n)/max(n) → int, other tokens as written. Full constraint table and unsupported forms: route-grammar.md.

FastEndpoint generation (on the outer operation class)

Both web-server and api-server host endpoints generated from contracts — there are no hand-written MVC BaseEndpoint shims in the template. Opt in per operation:

AttributeEffectUse when
[ApiEndpoint]Generator emits `BaseFastEndpoint<Op.Query\Command, Response>` for this operationEvery contract hosted on a server with EnableApiEndpointGeneration
[EndpointAuthorize(Policy=…, AuthenticationSchemes=…)]Generator emits Policies("…") / Roles(…) / AuthSchemes(…)Protected routes. Always set `AuthenticationSchemes` on hosted contracts — see scheme lists below
[EndpointAllowAnonymous(reason)]Generator emits AllowAnonymous()Genuinely public / pre-auth ceremony endpoints — reason is a required, honest, per-contract string
(no marker)Fail-closed (task 110): generator emits nothing, so FastEndpoints' own default (auth required) appliesNever — every [ApiEndpoint] contract must carry exactly one of the two markers above; TWA0013 flags the omission at build time

Picking anonymous when the contract shouldn't be is caught too: TWA0014 flags a contract that carries both markers, or [EndpointAllowAnonymous] alongside a nested Query/Command that declares IAuthApiRequest (manually or via [AuthApiRequest]) — see the auth-forms section below for why that combination is a contradiction, not just a style nit.

csharp
[ApiEndpoint]
[EndpointAuthorize
(
Policy = PermissionIds.IdentityRead,
AuthenticationSchemes = AuthenticationSchemeNames.AgentToken
)]
public static partial class GetAgentIdentity
{
[ApiRoute("api/identity/agent/me", HttpVerb.Get)]
public sealed partial class Query : IApiRequest, IQuery<OneOf<Response, SharedProblemDetails>>;
// …
}

Policy names (TWA0024): [EndpointAuthorize] Policy must equal a policy the hosting server registers (web: PermissionIds.*; named policies via AddPolicy). Prefer a contracts-visible const over a literal.

Scheme lists: hosted [EndpointAuthorize] must set AuthenticationSchemes (AuthenticationSchemeNames on web); permission policies carry no scheme list, so omitting it authenticates only the host default scheme. Details and per-surface table: endpoint-auth.md.

Validation stays on the mediator (FluentValidationBehavior). Do not re-validate in handlers and do not wire FastEndpoints' own FluentValidation integration (IncludeAbstractValidators = false). Handlers implement business logic only; the generated endpoint is pure HTTP plumbing.

Server projects set <EnableApiEndpointGeneration>true</EnableApiEndpointGeneration> and must set ApiEndpointContractAssemblies to the hosted contracts AssemblyName (web-contracts on web-server, api-contracts on api-server — not the namespace). Empty, mistyped, or unmarked names are TWE008. Hosted contracts assemblies apply [assembly: ApiEndpointsEmbedded]; host generators walk only marked refs.

HTTP verbs
OperationVerbUsual kind
QueryGetIQuery<…>
CreatePostICommand<…>
Update (full replace)PutIIdempotentCommand<…>
DeleteDeleteIIdempotentCommand<…>

The verb is the contract's [ApiRoute] verb. Generated FastEndpoints take it from there. Do not hand-write a second verb. Analyzer id TWA0005 (MVC verb vs [ApiRoute]) is retired and reserved.

The kind does not pick the verb, and the verb does not pick the kind. The column above is the usual pairing, not a rule: IIdempotentCommand promises that a retry is safe, so use ICommand when the handler cannot keep that promise. See neutral-operations.md.

Neutral operations: one contract, three bindings

The TimeWarp.Contracts package adds the transport-neutral part of a contract. Its version pin is its own line in Directory.Packages.props. The same request class is an HTTP endpoint, a CLI command, and an MCP tool:

  • Name, group, and title: [Operation("roles.get", Description = "…", Group = "…", Title = "…")]

on the nested Query/Command. A blank title means no title. [Destructive] and [OpenWorld] are tri-state: omitted is unset, not false. MCP treats an omitted destructive hint as true and reads it only when the tool is not read-only. Generators copy the hints only when [Operation] has a non-blank name. Kind still supplies read-only and idempotent.

  • Kind: the mediator interface (IQuery<T>, ICommand<T>, IIdempotentCommand<T>). It is

never an attribute argument.

  • Bindings: HTTP comes from [ApiRoute] + [ApiEndpoint] (this template). CLI and MCP come

from TimeWarp.Nuru, which reads the same contract and dispatches through the mediator.

TWA0035 (info) suggests [Operation] on a hosted contract that lacks one. Members, kind choice, bindings, and the catalog: neutral-operations.md.

Contract shell

Every operation is a public static partial class named for the operation:

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 90 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
0

Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/timewarpengineering/timewarp-architecture/tw-web-api-contracts/badge)](https://verified-skill.com/skills/timewarpengineering/timewarp-architecture/tw-web-api-contracts)

Something wrong with this skill or its page? Tell us.