Skip to content

Add dotnet-aspnetcore REST API authoring skills (controllers + Minimal APIs)#848

Draft
javiercn wants to merge 19 commits into
mainfrom
javiercn/aspnetcore-api-skills
Draft

Add dotnet-aspnetcore REST API authoring skills (controllers + Minimal APIs)#848
javiercn wants to merge 19 commits into
mainfrom
javiercn/aspnetcore-api-skills

Conversation

@javiercn

@javiercn javiercn commented Jun 30, 2026

Copy link
Copy Markdown
Member

ASP.NET Core REST API authoring skills (dotnet-aspnetcore)

Adds a disjoint set of eighteen skills that improve how the agent writes ASP.NET Core REST APIs with controllers and Minimal APIs. Each skill targets a genuine, measured gap that both a strong and a mid-tier model miss, and each ships with a convention-checking eval that proves it helps both without regressing either.

How these were built and measured

The skills were developed gap-first: for every candidate, the agent was first measured without the skill on natural, underspecified prompts (the kind a developer would actually write, with no hint of the convention), then with it. A skill is kept only when it helps both models with no meaningful regression; where the model is already strong unaided, no skill is added.

  • Domain: a realistic ARM-style control-plane API (MessagingApi: a messaging namespace aggregate owning queues, topics, subscriptions, authorization rules, with soft delete, row versions, and last-modified timestamps). None of this domain appears in any skill body; skills stay domain-agnostic.
  • Models: every A/B was run on Claude Sonnet and GPT-5.4, 3 runs per arm (5 for the borderline case), scored 1-5 per rubric criterion by an LLM judge.
  • Prompts never name the skill or cue the convention, so the baseline is honest. Rubrics check outcomes, not the skill's vocabulary.

Skills added (18) and eval results

Mean overall score (1-5), baseline → with-skill, natural prompts:

Skill Track Sonnet GPT-5.4 Gap it closes
controller-concurrency controller 3.0 → 5.0 2.5 → 4.5 Dual validators: adds the time-based Last-Modified validator (If-Modified-Since/If-Unmodified-Since) alongside the ETag both models already did
minimal-api-concurrency minimal 2.5 → 5.0 2.83 → 4.33 Same dual-validator gap, Minimal API form; ordinal header comparison
structure-api-business-logic shared 3.0 → 5.0 3.5 → 4.83 Move logic into an HTTP-agnostic service returning a Result with an error kind (no exceptions/tuples across the service boundary); thin endpoints
author-controller-endpoints controller 3.56 → 4.67 3.78 → 4.67 ActionResult<T>, correct status codes, CreatedAtAction + Location, ProducesResponseType, operation exposure
author-minimal-api-endpoints minimal 2.67 → 5.0 3.17 → 4.33 Strongly typed Results<> unions, TypedResults, MapGroup, 201+Location, ValidationProblem
controller-data-access controller 3.67 → 5.0 4.0 → 4.83 Keyset pagination over a deterministic total order (unique-key tiebreaker), next page as an RFC 8288 Link header, AsNoTracking + projection
minimal-api-data-access minimal 4.0 → 5.0 3.67 → 4.83 Keyset pagination over a deterministic total order (unique-key tiebreaker), next page as an RFC 8288 Link header, AsNoTracking + projection
test-apis-with-webapplicationfactory shared 4.4 → 5.0 4.6 → 4.8 Drive the real pipeline via WebApplicationFactory, replace the registered DbContext, isolate the database per test, assert status and body
authorize-api-endpoints shared 2.33 → 4.56 1.89 → 3.78 Authorize through the framework, not inline checks: declarative policy + handler reading context.Resource (HttpContext) route/endpoint metadata for route/claim rules; imperative AuthorizeAsync with the loaded entity for ownership; named policies, OR-semantics, 401/403/404, middleware order
minimal-api-parameter-binding minimal 2.0 → 5.0 2.0 → 5.0 Bind a custom/value type from the route or query via a static TryParse/BindAsync so parsing and validation happen at the boundary and invalid input is 400 before the handler runs
minimal-api-endpoint-filters minimal 1.0 → 5.0 2.0 → 5.0 Use endpoint filters only for concerns that need the bound (validated) arguments or the result and that no built-in already covers: rewrite a bound argument before the handler, and transform the result (field selection); route validation to AddValidation, other cross-cutting work to middleware
rate-limiting shared 2.0 → 4.67 2.0 → 4.33 Throttle with the built-in rate limiter (AddRateLimiter/UseRateLimiter) partitioned per caller (claim/API key/IP), 429 + Retry-After, deliberate algorithm — not a hand-rolled counter
long-running-operations shared 3.0 → 5.0 3.67 → 5.0 Model slow work as an async operation: 202 Accepted + a distinct pollable operation-status resource (running/succeeded/failed), not a blocked request
change-tracking-delta shared 2.17 → 4.33 2.17 → 4.0 Incremental sync: fetch only added/updated/removed since a watermark. Monotonic change marker (rowversion, order-preserving; timestamp fallback), soft-delete tombstones, total order + MIN_ACTIVE_ROWVERSION boundary, multipart/mixed of application/http (200 upsert / 204 removed) keeping payloads unchanged, Link: rel="deltaLink", 410 on stale token
structured-logging shared 3.0 → 5.0 3.0 → 4.0 Message templates with named placeholders (not interpolation), BeginScope correlation, levels by severity, exception-as-argument, no secrets/PII
patch-partial-updates shared 3.67 → 5.0 3.67 → 4.67 PATCH that changes only sent fields and distinguishes explicit null (clear) from absent (leave), via JSON Merge Patch over JsonElement/JsonNode (or JsonPatchDocument / tri-state Optional)
design-collection-api shared 2.33 → 4.67 3.0 → 5.0 Design a complete collection API and reconcile it with the data model: decide each cross-cutting axis (stable order, pagination, filtering, field selection, delta-readiness, concurrency, status codes) and add the columns/indexes each needs (order key + id tiebreaker, rowversion watermark, soft-delete, concurrency token), routing each capability to its own skill. Closes the both-model gap where a naive "list the resource" request omits filtering and change tracking and ignores the persistence columns they require
filter-and-select shared 3.33 → 4.33 4.0 → 4.33 Bounded, opt-in filtering (allow-list + Stripe-style bracket operators) and field selection (sparse fieldsets) translated to EF Where/Select: 400 on unknown field/operator, operands coerced to the field type, the keyset sort key kept in the projection so paging stays stable, and amplification guards (gate leading-wildcard Contains in favor of sargable StartsWith, cap predicate count and page size)

No rubric criterion regresses on either model for any kept skill.

Note on the authorization skill

authorize-api-endpoints was added after a second, probe-driven round. Both models default to inline if (User…) checks and do not reach for the authorization framework at all: the baseline for the framework criteria (requirement + handler, named-policy opt-in, imperative AuthorizeAsync for loaded-entity rules, context.Succeed OR-semantics) scored ~1/5 on both models, making this the largest single gap found. The skill leads with the rule of thumb that decides the mechanism: where does the decision data live? Route values and claims are decided declaratively by a handler that reads context.Resource as the HttpContext (and custom endpoint metadata); stored entity state (an OwnerId) is decided imperatively with IAuthorizationService.AuthorizeAsync after loading.

Note on de-cueing the prompts

minimal-api-parameter-binding and minimal-api-endpoint-filters were initially measured as "no gap" because their probe prompts cued the technique (for example "use a strongly typed value in the handler signature" or "apply this across the route group without repeating"). A cued prompt hands the baseline the same guidance the skill provides, so the baseline looked strong (5.0/5.0). Rewriting the prompts to describe only the user-facing goal collapsed the baselines to ~2.0, exposing genuine both-model gaps that the skills then close. Every prompt in these evals describes a goal, never a technique the skill teaches.

Skills considered and not built (14)

Every candidate below was measured the same way (natural prompts, both models, LLM-judged rubric). A skill is kept only when it helps both models with no meaningful regression. These were dropped; this list is a record so they are not re-attempted without new evidence.

Already strong on both models — no gap to close

A skill here would only spend context tokens and risk regressing a model that is already at or near ceiling.

Candidate Sonnet GPT-5.4 What it would have taught / why dropped
design-api-operations 5.0 4.0 Resource-operation modeling: nesting child resources under aggregates, lifecycle transitions as explicit intent-revealing operations. Sonnet at ceiling; GPT-5.4 only scattered modest gaps. Distinct from the shipped design-collection-api, which covers cross-cutting collection completeness and data-model reconciliation, not resource modeling
model-controller-payloads 5.0 5.0 Request/response DTOs, avoiding entity leakage, over-posting, and navigation cycles, projecting summaries. Done unprompted
model-minimal-api-payloads 5.0 4.83 Same, Minimal API form. Payload modeling already strong
options-validation 5.0 4.67 IOptions<T> with ValidateDataAnnotations / ValidateOnStart. Already applied
transactions-outbox 5.0 5.0 Transactional outbox / reliable dispatch of side effects. Produced unprompted from "notify reliably even when the downstream is sometimes unavailable"
polymorphic-json 5.0 5.0 [JsonPolymorphic] / [JsonDerivedType] with an explicit discriminator. Already done
health-checks 5.0 4.67 AddHealthChecks with liveness/readiness endpoints. Already done
opentelemetry 5.0 5.0 Vendor-neutral OpenTelemetry tracing/metrics wiring. Already done without a vendor-neutral hint
http-header-forwarding 5.0 4.67 ForwardedHeaders middleware when running behind a reverse proxy. Already done
consume-apis (.NET client) 4.67 4.0 Client-side consumption: follow rel=next to completion, persist the delta link and resync on 410, send If-Match and handle 412, honor Retry-After on 429, poll 202, use IHttpClientFactory. Strong when the contract is spelled out and when it is only discoverable in an API-reference doc with a goal-only task (de-cued), so the high score is genuine competence, not cueing
consume-apis (JS client) 4.5 4.33 Same conventions from a fetch client (AbortController, backoff, Link parsing). Already strong

Asymmetric or borderline — one skill cannot help both models

A real gap exists on only one model (the other is at ceiling), or the two models are weak on different halves of the problem, so a single skill cannot help both without regressing one.

Candidate Sonnet GPT-5.4 Why dropped
http-resilience 5.0 2.5 Polly / Microsoft.Extensions.Http.Resilience retry, timeout, circuit breaker. A real GPT-5.4 gap, but Sonnet is at ceiling, so a skill risks regressing Sonnet
api-error-handling 2.33 4.33 Consistent ProblemDetails via IExceptionHandler / UseExceptionHandler. A real Sonnet gap, but GPT-5.4 is already strong. Asymmetric
ef-bulk-query-shaping 3.5 3.67 Bulk ExecuteUpdate/ExecuteDelete and AsSplitQuery to avoid cartesian explosion. Each model is strong on the half the other is weak on, so there is no consistent both-model gap

Cross-model validation (a third family: MAI-Code-1-Flash)

To check the skills are not overfit to the two frontier models they were tuned on, a spot-check ran a smaller, faster third-family model (MAI-Code-1-Flash) on a representative slate.

The skills lift the weaker model too (baseline → with skill, natural prompts):

Skill MAI-Code-1-Flash Sonnet / GPT-5.4 (reference)
authorize-api-endpoints 1.00 → 4.20 (+3.20) 2.33→4.56 / 1.89→3.78
minimal-api-parameter-binding 2.00 → 4.67 (+2.67) 2.0→5.0 / 2.0→5.0
design-collection-api 2.00 → 3.33 (+1.33) 2.33→4.67 / 3.00→5.00
filter-and-select 2.50 → 3.33 (+0.83) 3.33→4.33 / 4.00→4.33

The weaker model starts lower and gains the most where the gap is largest (authorization, parameter binding), so the guidance is not specific to frontier models.

The "no gap" drops are mostly robust across tiers, with one exception. Re-measuring the dropped candidates' baselines on MAI-Code-1-Flash:

Dropped area MAI base Sonnet / GPT-5.4 Holds across tiers?
polymorphic-json 4.67 5.0 / 5.0 yes
transactions-outbox 4.67 5.0 / 5.0 yes
model-minimal-api-payloads 4.83 5.0 / 4.83 yes
design-api-operations 4.17 5.0 / 4.0 yes
consume-apis (.NET client) 4.00 4.67 / 4.0 yes
health-checks 2.33 5.0 / 4.67 no — weak-model gap

Five of six drops hold: the weaker model also handles those areas well, so they are genuinely no-gap rather than frontier-only. The exception is health-checks — the frontier models add liveness/readiness checks unprompted, but MAI-Code-1-Flash mostly does not (2.33). It stays dropped for the models these skills target, but is recorded here as a weak-model-tier candidate: a health-checks skill would likely help smaller/cheaper models even though it is unnecessary for frontier ones.

Notable findings baked into the skills

  • The genuine cross-model gaps are the non-obvious conventions, not the basics: the dual ETag + Last-Modified validators, the service + Result structure, bounded pagination, and per-test database isolation. Endpoint and payload basics are already strong on both models.
  • Scope the eval to the skill's responsibility. Early versions of the endpoint evals bundled domain-modeling rubrics (uniqueness, soft delete, parent existence) that the models already nail; the extra surface caused attention-theft that looked like a regression. Rescoping each eval to its skill's actual concern turned both endpoint skills into clean both-model wins.
  • Don't cue the convention in the prompt. A cued test-apis prompt inflated the baseline to 5.0/4.67 and hid the real gap; a de-cued prompt exposed a real GPT-5.4 weakness in replacing and isolating the DbContext, which the skill then closes.
  • Two concrete skill fixes came out of the measurements: removing a hard-delete example that cued the wrong delete semantics in author-controller-endpoints, and switching test-apis from a class-shared factory to a per-test factory so each test gets a clean in-memory database.

Layout

  • Skills: plugins/dotnet-aspnetcore/skills/<name>/SKILL.md
  • Evals + fixtures: tests/dotnet-aspnetcore/<name>/ (eval.vally.yaml + a fixture/MessagingApi project)

Opening as a draft for review of the skill content and the eval methodology before finalizing.

javiercn and others added 10 commits June 29, 2026 20:37
…logic structure

Replaces the prior dotnet-aspnetcore skills (dotnet-webapi, minimal-api-file-upload, configuring-opentelemetry-dotnet, convert-blazor-server-to-webapp) with a focused REST API skill set.

Adds two skills with Vally evals over a shared e-commerce store fixture (1:1, 1:M, M:N relationships):

- author-minimal-api-endpoints: typed Results<...>/TypedResults, correct status codes (201+Location, 204, 404, 400), input validation via ProblemDetails.

- structure-api-business-logic: thin endpoints over a service layer that returns a hand-rolled Result<T> with a semantic ErrorType; endpoint translates it to HTTP; service takes DTOs/commands with manual mapping and never returns ASP.NET result types.

Both validated baseline-vs-skilled over 3 runs (endpoints 3.17->4.92/5, structure 2.56->5.00/5; no regressions).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Optimistic concurrency + HTTP conditional requests for minimal APIs: concurrency token -> ETag, If-None-Match (304), If-Match (412)/428, and DbUpdateConcurrencyException handling. Baseline 3.89 -> 5.00 over 3 runs; the value concentrates in the If-Match conditional-update gap (1.67 -> 5.00).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Controller-based Web API actions with correct result types and status codes: ControllerBase + [ApiController], ActionResult<T> with typed helpers, CreatedAtAction (201+Location), 204/404, [ProducesResponseType] for discoverability, and data-annotation validation (auto-400). Baseline 3.89 -> 4.67 over 3 runs (+16%), no regressions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
End-to-end integration testing with WebApplicationFactory<Program>: in-memory host + HttpClient, making Program reachable, replacing the DbContext with an isolated per-factory database, per-test seeding, and asserting status + payload. Baseline 4.33 -> 4.89 over 3 runs (+11%); value concentrated in per-test seeding discipline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Teaches optimistic concurrency and HTTP conditional requests for controller
actions, exposing both validators a resource offers: an ETag from the
concurrency token (If-Match/If-None-Match) and Last-Modified from the
last-modified timestamp (If-Modified-Since/If-Unmodified-Since), with
304/412/428 handling and DbUpdateConcurrencyException mapping.

Natural-prompt eval (MessagingApi fixture, 3 runs/model):
  sonnet  3.0 -> 5.0
  gpt-5.4 2.5 -> 4.5
Both gap criteria (Last-Modified validator; ETag-vs-content distinction)
closed on both models with no regression.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the Last-Modified time validator (If-Modified-Since/If-Unmodified-Since)
alongside the existing ETag content validator, a ConditionalRequest helper that
reads both off the entity, ordinal header comparisons, 304/412/428 handling,
and DbUpdateConcurrencyException mapping. Rebuilds the eval on the MessagingApi
domain (minimal-API-wired) with natural prompts and dual-validator rubrics.

Natural-prompt eval (3 runs/model):
  sonnet  2.5  -> 5.0
  gpt-5.4 2.83 -> 4.33
Last-Modified validator gap (baseline ~1 on both) closed on both models.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replaces the StoreApi fixture and cued prompts with natural business-logic
scenarios (multi-entity rules: create-queue with parent/unique/quota checks;
decommission-namespace cascade) and convention-checking rubrics for service
extraction, a Result-with-error-kind outcome (no exceptions/tuples across the
service boundary), HTTP-agnostic services, and thin endpoints.

Natural-prompt eval (3 runs/model):
  sonnet  3.0 -> 5.0
  gpt-5.4 3.5 -> 4.83
Weak baseline criteria (logic-in-service, thin endpoint, result-value)
closed on both models, no regression.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Both author-controller-endpoints and author-minimal-api-endpoints cleanly help
both models once evaluated on their actual scope (routing, status codes, typed
results, operation exposure) rather than bundled domain-modeling rubrics that
the models already handle and that caused attention-theft regressions.

Skill fixes: author-controller delete example no longer cues a hard delete
(soft-delete-aware), and braceless control flow replaced with Allman braces in
both skills.

Rebuilt evals on the MessagingApi domain (controller-wired and minimal-wired)
with natural prompts and endpoint-only rubrics. Natural-prompt eval (3 runs/model):
  author-controller-endpoints  sonnet 3.56 -> 4.67,  gpt-5.4 3.78 -> 4.67
  author-minimal-api-endpoints sonnet 2.67 -> 5.0,   gpt-5.4 3.17 -> 4.33
No criterion regresses on either model.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…oints

Both controller-data-access and minimal-api-data-access teach paginating every
collection endpoint with a bounded page size, ordering before Skip/Take in the
query, reading with AsNoTracking, and projecting to a DTO in the query.

The natural-prompt baseline showed pagination is the one real gap (the models
already do no-tracking and projection): a list endpoint returns the whole table
unless paged, most acutely on sonnet (pagination criterion ~1/5).

Natural-prompt eval (3 runs/model):
  controller-data-access   sonnet 3.33 -> 5.0,  gpt-5.4 4.83 -> 5.0
  minimal-api-data-access  sonnet 3.5  -> 5.0,  gpt-5.4 4.33 -> 5.0
No criterion regresses on either model.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Each test now owns its WebApplicationFactory (xUnit constructs the test class
per method), so every test gets a fresh in-memory database and cannot see other
tests' writes; IClassFixture is reserved for read-only or uniquely-keyed sharing.
This removes the isolation regression a class-shared factory caused.

Rebuilt the eval on the MessagingApi domain with an endpoints-implemented
fixture (NamespacesController) and a de-cued natural prompt. Natural-prompt eval
(5 runs/model):
  sonnet  4.4 -> 5.0  (isolation 3.8 -> 5.0)
  gpt-5.4 4.6 -> 4.8  (DbContext replacement 4.2 -> 5.0)
The skill closes the genuine DbContext-replacement and isolation gap.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Skill Coverage Report

Plugin Skill Covered Coverage
dotnet-msbuild extension-points 0/1 0%
dotnet-blazor author-component 0/1 0%
dotnet-blazor collect-user-input 0/4 0%
dotnet-blazor configure-auth 0/3 0%
dotnet-blazor coordinate-components 0/2 0%
dotnet-blazor fetch-and-send-data 1/5 20%
dotnet-blazor plan-ui-change 0/1 0%
dotnet-blazor support-prerendering 0/3 0%
dotnet-blazor use-js-interop 0/2 0%
dotnet-advanced `` error
⚠️ dotnet csharp-scripts 13/22 59.1%
dotnet dotnet-pinvoke 6/28 21.4%
dotnet setup-local-sdk 3/8 37.5%
dotnet-aspnetcore author-controller-endpoints 0/7 0%
dotnet-aspnetcore authorize-api-endpoints 0/2 0%
dotnet-aspnetcore change-tracking-delta 0/3 0%
dotnet-aspnetcore controller-concurrency 0/3 0%
dotnet-aspnetcore controller-data-access 0/3 0%
dotnet-aspnetcore design-collection-api 0/1 0%
dotnet-aspnetcore filter-and-select 0/3 0%
dotnet-aspnetcore long-running-operations 0/3 0%
dotnet-aspnetcore minimal-api-concurrency 0/2 0%
dotnet-aspnetcore minimal-api-data-access 0/1 0%
dotnet-aspnetcore minimal-api-endpoint-filters 0/1 0%
dotnet-aspnetcore minimal-api-parameter-binding 0/1 0%
dotnet-aspnetcore patch-partial-updates 0/3 0%
dotnet-aspnetcore rate-limiting 0/1 0%
dotnet-aspnetcore structure-api-business-logic 0/3 0%
dotnet-aspnetcore test-apis-with-webapplicationfactory 0/2 0%
Uncovered: dotnet-msbuild/extension-points
  • [CodePattern] [MSBuild] (line 184)
Uncovered: dotnet-blazor/author-component
  • [CodePattern] [Parameter] (line 31)
Uncovered: dotnet-blazor/collect-user-input
  • [CodePattern] [SupplyParameterFromForm] (line 31)
  • [CodePattern] [CascadingParameter] (line 162)
  • [CodePattern] [Range] (line 135)
  • [CodePattern] [Required] (line 135)
Uncovered: dotnet-blazor/configure-auth
  • [CodePattern] [CascadingParameter] (line 51)
  • [CodePattern] [ExcludeFromInteractiveRouting] (line 152)
  • [CodePattern] [Authorize] (line 101)
Uncovered: dotnet-blazor/coordinate-components
  • [CodePattern] [CascadingParameter] (line 63)
  • [CodePattern] readonly (line 137)
Uncovered: dotnet-blazor/fetch-and-send-data
  • [CodePattern] [PersistentState] (line 84)
  • [CodePattern] [StreamRendering] (line 74)
  • [CodePattern] [SupplyParameterFromQuery] (line 159)
  • [CodePattern] [Parameter] (line 159)
Uncovered: dotnet-blazor/plan-ui-change
  • [CodePattern] [Parameter] (line 64)
Uncovered: dotnet-blazor/support-prerendering
  • [CodePattern] [ExcludeFromInteractiveRouting] (line 148)
  • [CodePattern] [CascadingParameter] (line 159)
  • [CodePattern] [PersistentState] (line 39)
Uncovered: dotnet-blazor/use-js-interop
  • [CodePattern] sealed (line 118)
  • [CodePattern] readonly (line 118)
Uncovered: dotnet/csharp-scripts
  • [Validation] dotnet --version reports 10.0 or later (or fallback path is used) (line 274)
  • [Validation] The app compiles without errors (can be checked explicitly with dotnet build <file>.cs) (line 276)
  • [Validation] App files and cached artifacts are cleaned up after the session (line 279)
  • [Pitfall] #:package without a version (line 286)
  • [Pitfall] #:property with wrong syntax (line 287)
  • [Pitfall] Directives placed after C# code (line 288)
  • [Pitfall] Reflection-based JSON serialization fails (line 293)
  • [WorkflowStep] Step 5: Clean up (line 199)
  • [CodePattern] [MSBuild] (line 93)
Uncovered: dotnet/dotnet-pinvoke
  • [Validation] Calling convention specified if targeting Windows x86; omitted otherwise (line 416)
  • [Validation] String encoding is explicit — no reliance on defaults or CharSet.Auto (line 417)
  • [Validation] Memory ownership is documented and matched (who allocates, who frees, with what) (line 418)
  • [Validation] SafeHandle used for all native handles (no raw IntPtr escaping the interop layer) (line 419)
  • [Validation] Delegates passed as callbacks are rooted to prevent GC collection (line 420)
  • [Validation] SetLastError/SetLastPInvokeError set for APIs that use OS error codes (line 421)
  • [Validation] Struct layout matches native (packing, alignment, field order) (line 422)
  • [Validation] CLong/CULong used for C long/unsigned long in cross-platform code (line 423)
  • [Validation] If using CLong/CULong with LibraryImport, [assembly: DisableRuntimeMarshalling] is applied (line 424)
  • [Validation] No bool without explicit MarshalAs — always specify UnmanagedType.Bool (4-byte) or UnmanagedType.U1 (1-byte) to ensure normalization across the language boundary. (line 425)
  • [WorkflowStep] Step 5: Establish Memory Ownership (line 168)
  • [WorkflowStep] Step 6: Use SafeHandle for Native Handles (line 224)
  • [WorkflowStep] Step 7: Handle Errors (line 261)
  • [CodePattern] [256] (line 176)
  • [CodePattern] sealed (line 228)
  • [CodePattern] [UnmanagedCallersOnly] (line 281)
  • [CodePattern] [UnmanagedFunctionPointer] (line 300)
  • [CodePattern] [MarshalAs] (line 144)
  • [CodePattern] [DllImport] (line 93)
  • [CodePattern] [LibraryImport] (line 101)
  • [CodePattern] [UnmanagedCallConv] (line 111)
  • [CodePattern] [Out] (line 144)
Uncovered: dotnet/setup-local-sdk
  • [Pitfall] paths ignored (line 382)
  • [Pitfall] dotnet app.dll wrong runtime (line 386)
  • [CodePattern] [guid] (line 104)
  • [CodePattern] [pscustomobject] (line 301)
  • [CodePattern] [ordered] (line 301)
Uncovered: dotnet-aspnetcore/author-controller-endpoints
  • [CodePattern] [Range] (line 84)
  • [CodePattern] [ProducesResponseType] (line 16)
  • [CodePattern] [HttpPost] (line 50)
  • [CodePattern] [HttpGet] (line 16)
  • [CodePattern] [ApiController] (line 16)
  • [CodePattern] [HttpDelete] (line 50)
  • [CodePattern] [Route] (line 16)
Uncovered: dotnet-aspnetcore/authorize-api-endpoints
  • [CodePattern] [Authorize] (line 74)
  • [CodePattern] sealed (line 31)
Uncovered: dotnet-aspnetcore/change-tracking-delta
  • [CodePattern] CancellationToken (line 68)
  • [CodePattern] sealed (line 68)
  • [CodePattern] [HttpGet] (line 115)
Uncovered: dotnet-aspnetcore/controller-concurrency
  • [CodePattern] CancellationToken (line 95)
  • [CodePattern] [HttpGet] (line 95)
  • [CodePattern] [HttpPut] (line 117)
Uncovered: dotnet-aspnetcore/controller-data-access
  • [CodePattern] CancellationToken (line 29)
  • [CodePattern] [HttpGet] (line 29)
  • [CodePattern] [FromQuery] (line 29)
Uncovered: dotnet-aspnetcore/design-collection-api
  • [CodePattern] [Timestamp] (line 52)
Uncovered: dotnet-aspnetcore/filter-and-select
  • [CodePattern] sealed (line 26)
  • [CodePattern] [gte] (line 70)
  • [CodePattern] readonly (line 26)
Uncovered: dotnet-aspnetcore/long-running-operations
  • [CodePattern] CancellationToken (line 22)
  • [CodePattern] [ProducesResponseType] (line 22)
  • [CodePattern] [HttpPost] (line 22)
Uncovered: dotnet-aspnetcore/minimal-api-concurrency
  • [CodePattern] [ConcurrencyCheck] (line 29)
  • [CodePattern] [Timestamp] (line 29)
Uncovered: dotnet-aspnetcore/minimal-api-data-access
  • [CodePattern] CancellationToken (line 29)
Uncovered: dotnet-aspnetcore/minimal-api-endpoint-filters
  • [CodePattern] sealed (line 52)
Uncovered: dotnet-aspnetcore/minimal-api-parameter-binding
  • [CodePattern] readonly (line 24)
Uncovered: dotnet-aspnetcore/patch-partial-updates
  • [CodePattern] [HttpPatch] (line 31)
  • [CodePattern] [FromBody] (line 31)
  • [CodePattern] CancellationToken (line 31)
Uncovered: dotnet-aspnetcore/rate-limiting
  • [CodePattern] CancellationToken (line 23)
Uncovered: dotnet-aspnetcore/structure-api-business-logic
  • [CodePattern] CancellationToken (line 44)
  • [CodePattern] sealed (line 23)
  • [CodePattern] readonly (line 23)
Uncovered: dotnet-aspnetcore/test-apis-with-webapplicationfactory
  • [CodePattern] Assert.Equal (line 71)
  • [CodePattern] readonly (line 25)

javiercn and others added 9 commits June 30, 2026 22:01
…ve vs imperative)

Teaches authorizing API endpoints through the authorization framework instead of
inline checks, choosing the mechanism by where the decision data lives:
declarative policy + AuthorizationHandler reading context.Resource as HttpContext
(route values + endpoint metadata) for route/claim-derivable rules, and imperative
IAuthorizationService.AuthorizeAsync with the loaded entity for ownership/state
rules. Covers requirement+handler mechanics, OR-semantics (Succeed never Fail),
named policies, a fallback policy, 401/403/404, DI lifetimes, and middleware order.

The natural-prompt baseline showed both models default to inline if-checks and do
not reach for the framework (the declarative-vs-imperative distinction scored ~1).

Natural-prompt eval (3 scenarios, 3 runs/model):
  sonnet  2.33 -> 4.56
  gpt-5.4 1.89 -> 3.78
No criterion regresses on either model.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ills

Two minimal-API gaps that the de-cued natural-prompt baseline exposed (cued
prompts had hidden them by naming the technique):

- minimal-api-parameter-binding: bind a custom/value type from the route or query
  via a static TryParse or BindAsync so parsing and validation happen at the
  boundary and invalid input is 400 before the handler runs, instead of taking a
  raw string and validating inline.
- minimal-api-endpoint-filters: apply cross-cutting validation and required-header
  checks as endpoint filters on a route group (short-circuiting with a 400),
  instead of duplicating the checks in every handler.

Natural-prompt eval (3 runs/model):
  minimal-api-parameter-binding  sonnet 2.0  -> 5.0,  gpt-5.4 2.0  -> 5.0
  minimal-api-endpoint-filters   sonnet 2.33 -> 5.0,  gpt-5.4 2.33 -> 5.0
No criterion regresses on either model.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Endpoint filters earn their place only when a concern (1) needs the bound,
already-validated arguments or the handler result, and (2) is not already
covered by a built-in mechanism with the same access. Plain input validation
fails test 2 (built-in AddValidation covers it), so the skill no longer teaches
a validation filter.

The two examples now exercise exactly what nothing else can: rewriting a bound
argument before the handler (normalize a value) and changing the returned result
(field selection from a query value). Adds a When-not-to-use-a-filter section
routing validation to AddValidation, cross-cutting request/response concerns to
middleware, authz to policies, exceptions to IExceptionHandler, and response
caching to output caching. Eval rebuilt around the arg-rewrite + result-shape
scenario.

Natural-prompt eval (3 runs/model):
  sonnet  1.0 -> 5.0
  gpt-5.4 2.0 -> 5.0
No criterion regresses on either model.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two gaps confirmed by de-cued natural-prompt probing on both models:

- rate-limiting: throttle with the built-in rate limiter (AddRateLimiter +
  UseRateLimiter) partitioned per caller (a claim/API key/IP), 429 with
  Retry-After, a deliberate algorithm. Both models get the concept right but
  hand-roll a counter instead of reaching for the built-in (that criterion 1/5).
- long-running-operations: model slow work as an async operation - accept with
  202 and a Location to a distinct operation-status resource the client polls
  through a running/succeeded/failed lifecycle, rather than blocking the request.
  Both models do async 202 but omit the separate operation-status resource.

Natural-prompt eval (3 runs/model):
  rate-limiting             sonnet 2.0 -> 4.67,  gpt-5.4 2.0  -> 4.33
  long-running-operations   sonnet 3.0 -> 5.0,   gpt-5.4 3.67 -> 5.0

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…Link header

The shipped pagination guidance was incomplete and its example unstable: it
ordered by a non-unique column (Name) with Skip/Take, which repeats or skips
rows when the sort field ties or the collection changes between requests, and it
returned a page-number envelope.

Both controller-data-access and minimal-api-data-access now teach:
- a deterministic total order ending in a unique key (ThenBy the id),
- keyset/cursor paging (WHERE (sortKey, id) > (lastSortKey, lastId)) instead of
  Skip/OFFSET, seeking on an immutable key,
- the next page delivered as an RFC 8288 Link header (rel=next) with the body as
  the bare collection.

Natural-prompt eval, upgraded to a robust-pagination scenario (3 runs/model):
  controller-data-access   sonnet 3.67 -> 5.0,  gpt-5.4 4.0  -> 4.83
  minimal-api-data-access  sonnet 4.0  -> 5.0,  gpt-5.4 3.67 -> 4.83
The Link-header criterion went 1 -> 5 on both models; no criterion regresses.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Teaches an incremental change-tracking (delta) endpoint: after an initial sync a
client fetches only what changed - added, updated, removed - since a watermark.
Covers a monotonic change marker (a rowversion mapped order-preserving as a
comparable number, with an app-maintained timestamp fallback that overlaps and
dedups when no rowversion exists), soft-delete tombstones, a total order over
(marker, id), the in-flight-commit boundary hazard (MIN_ACTIVE_ROWVERSION), and
a multipart/mixed of application/http response (200 present / 410 removed) that
keeps entity payloads unchanged, with the change-tracking link in the Link header
(rel=deltaLink) and 410 on a stale token. No Atom.

Natural-prompt eval, two scenarios (rowversion available / not), 3 runs/model:
  sonnet  2.17 -> 4.33
  gpt-5.4 2.17 -> 4.0
Multipart representation, Link-header token, and no-changes gaps (~1-2 baseline)
close to ~5; no criterion regresses.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two gaps that only surfaced after de-cuing the probe prompts (the cued versions
had hidden them):

- structured-logging: message templates with named placeholders (not
  interpolation), a scope (BeginScope) to correlate an operation's lines,
  levels by severity, exceptions passed as the exception argument, no secrets/PII.
- patch-partial-updates: PATCH that changes only the fields sent and
  distinguishes an explicit null (clear) from an omitted field (leave), via JSON
  Merge Patch over JsonElement/JsonNode (or JsonPatchDocument / a tri-state
  Optional), instead of a plain DTO where absent and null collapse.

De-cued natural-prompt eval (3 runs/model):
  structured-logging      sonnet 3.0  -> 5.0,  gpt-5.4 3.0  -> 4.0
  patch-partial-updates   sonnet 3.67 -> 5.0,  gpt-5.4 3.67 -> 4.67
No criterion regresses on either model.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Switch the tombstone from 410 Gone to a body-less 204 No Content so every
multipart part stays in the success range (a DELETE-style outcome) - the client
reads body-present as upsert and no-body as remove. 410 is kept only for an
expired change-tracking token. Adds a rendered on-the-wire sample (200 upsert /
204 removed + deltaLink) and a note that 201 Created is worthwhile only when a
creation marker is tracked separately from the last-change marker. Rubric accepts
204 or 410 for the tombstone.

Natural-prompt eval, two scenarios, 3 runs/model:
  sonnet  2.17 -> 4.5
  gpt-5.4 2.0  -> 4.17

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
design-collection-api: greenfield collection-API completeness + data-model
reconciliation (stable order, pagination, filtering, delta-readiness,
concurrency, status codes) routing to the per-capability skills.
Eval: sonnet 2.33->4.67, gpt-5.4 3.00->5.00.

filter-and-select: bounded, opt-in filtering (allow-list + bracket operators)
and field selection (sparse fieldsets) translated to EF Where/Select, with
type coercion and amplification guardrails.
Eval: sonnet 3.33->4.33, gpt-5.4 4.00->4.33.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant