AI Skill Report Card

Backend & Data

A-85·Oct 15, 2026·Source: Extension-selection

Backend & Data

Implement backend functionality that is correct beyond the happy path: data integrity, authorization, concurrency, and failure handling are first-class requirements, not afterthoughts.

13 / 15

Before writing code, answer these four questions from actual project evidence (not assumption):

  1. What already exists? Read the relevant route/controller, model/schema, migration history, and existing tests.
  2. What are the real contracts? Existing request/response shapes, status codes, error formats, auth mechanism — don't invent new conventions.
  3. What must stay consistent? Which invariants, constraints, or multi-step operations need transactional or concurrency protection.
  4. What is the smallest correct change? Don't add versioning, pagination, or zero-downtime migration machinery the project doesn't need or use elsewhere.

Then implement, applying the checklists below, and verify with the project's actual test/lint/build tools.

Recommendation▾
Add a couple of concrete before/after code snippets (e.g., actual mass-assignment fix) rather than relying solely on prose scenarios
14 / 15
Progress:
- [ ] Inspect existing code, schema, migrations, tests, dependency versions
- [ ] Identify invariants, ownership rules, and consumers affected
- [ ] Implement validation, business logic, and persistence
- [ ] Apply transactions/concurrency control where multiple writes must be atomic
- [ ] Apply authorization checks at the resource/field level
- [ ] Handle error paths with safe, consistent responses
- [ ] Write/update migration with compatibility and rollback considered
- [ ] Run tests, type checks, lint, and manual/integration verification
- [ ] Report what was verified vs. assumed vs. unknown

1. Inspect before implementing

  • Scale inspection to task size: a single-field validation fix needs only the touched file and its tests; a cross-service contract change or migration needs the schema, migration history, consumers, and related services.
  • Never invent endpoints, fields, model names, library APIs, or versions. If uncertain, check the dependency manifest/lockfile, official docs, or state explicitly what is unverified.

2. Implement the logic

  • Match existing architecture layering (controller/service/repository, or whatever the project uses) — don't introduce a new pattern unilaterally.
  • Validate all untrusted input server-side: type, required fields, length/range, allowed field set. Reject unknown/protected fields explicitly (don't mass-assign request bodies to models).
  • Map failure conditions to correct status codes/error shapes consistent with existing error handling in the codebase.
  • Don't leak stack traces, internal identifiers, or secrets in error responses or logs.

3. Protect data integrity

  • Use DB-level constraints (PK/FK/unique/not-null/check) for invariants that matter, not just app-level checks.
  • Wrap multi-step writes in a transaction; decide explicitly what happens on partial failure (rollback, compensating action, or idempotent retry).
  • Consider concurrent access: use row locks, optimistic concurrency (version/updated_at columns), or unique constraints where two requests could race.
  • Always use parameterized queries / the ORM's safe query builder — never string-concatenate untrusted input into SQL.

4. Enforce access control

  • Every operation touching a resource must verify the caller is authorized for that specific resource, not just authenticated. Never trust a client-supplied ID as proof of ownership.
  • Reuse the existing auth/session mechanism; don't build a parallel one.
  • Check HTTP method/operation semantics: GET/HEAD must stay safe (no side effects); only retry idempotent operations automatically. For non-idempotent operations exposed to retries (payments, order creation), use an idempotency key or dedup check.

5. Handle migrations safely

  • Read existing migration history and current production-like data assumptions first.
  • For additive, low-risk changes (new nullable column, new table): a single straightforward migration is fine.
  • For risky changes (new required column, column rename/drop, type change on populated table): plan backfill + compatibility window (e.g., add nullable → backfill → add NOT NULL constraint in a later step) and verify old application code won't break mid-rollout if deploy order isn't atomic.
  • Never silently drop data or columns without explicit authorization. Have a rollback/recovery note for destructive changes.
  • Verify engine-specific behavior (e.g., does ALTER TABLE ADD COLUMN NOT NULL lock the table on this DB version?) rather than assuming.

6. Verify

  • Run the project's real test suite, type checker, and linter — don't claim success without executing them.
  • For data/migration changes, verify against actual (or realistic sample) data, not just an empty test DB, when feasible.
  • State clearly what was verified, what was assumed, and what remains unchecked (e.g., "not tested against production data volume").

7. Coordinate, don't over-escalate

Hand off only when genuinely warranted:

  • architecture — new service boundaries, cross-component data ownership shifts.
  • frontend — contract changes affecting UI consumers.
  • debugging — root cause of a failure is not yet known.
  • security — deep auth/injection/sensitive-data review beyond standard checks.
  • performance — measurable bottleneck investigation.
  • testing-quality — broad regression/integration coverage.

Routine CRUD, validation, and migration work stays within this skill.

Recommendation▾
Trim the acceptance scenarios section slightly — it's thorough but borders on verbose for the format

A. Add a CRUD endpoint to an existing app

  • Inspect: existing route structure, naming conventions, response envelope, auth middleware, equivalent existing endpoints.
  • Decisions: reuse existing validation/middleware patterns; define DTO/response shape distinct from raw DB model if the project already does this; correct status codes (201 on create, 404 on missing, 409 on conflict).
  • Avoid: inventing a new response envelope; adding a dependency (e.g., a new validation library) when one is already in use; exposing internal-only fields.
  • Verify: endpoint tested for create/read/update/delete, invalid input rejected, run existing test suite.

B. Reject malformed/disallowed fields

  • Inspect: the model's writable fields vs. server-controlled fields (id, timestamps, role, ownerId).
  • Decisions: explicit allow-list for mass assignment; validate types/formats/lengths before hitting the DB; use parameterized queries regardless of validation.
  • Avoid: passing raw req.body to an ORM update/create call; relying on client-side validation; building SQL via string concatenation.
  • Verify: test with extra/forbidden fields (e.g., role: "admin"), malformed types, and oversized payloads — confirm rejection with a clear 400 and no DB write occurred.

C. Prevent cross-user resource access via ID substitution

  • Inspect: how the resource's ownership is modeled (owner_id/user_id field, join table, etc.) and current authorization middleware.
  • Decisions: every read/update/delete must filter or check against current_user ownership/role, not just existence of the ID; return 404 (not 403) if leaking existence is undesirable, per existing convention.
  • Avoid: trusting a path/body ID alone; checking ownership only on some HTTP verbs but not others (e.g., GET unprotected but DELETE protected).
  • Verify: write a test where user A requests/modifies user B's resource ID and confirm denial; confirm legitimate owner access still succeeds.

D. Multi-step business operation requiring consistency

  • Inspect: all tables/entities touched, whether DB supports transactions, existing transaction usage patterns in the codebase.
  • Decisions: wrap all writes in a single transaction; decide rollback vs. compensating action if an external (non-transactional) call is involved (e.g., payment API + DB write — use an outbox/idempotent reconciliation pattern rather than a bare transaction).
  • Avoid: partial writes left uncommitted on error; assuming an external API call can be rolled back by a DB transaction.
  • Verify: simulate failure at each step (e.g., force an exception after step 1 of 2) and confirm no partial state persists; check DB directly, not just the HTTP response.

E. Add a required field to an existing table

  • Inspect: current row count/nature of existing data, deployment process (single-step vs. rolling deploy), consumers reading/writing this table.
  • Decisions: for populated tables, migrate in stages — add nullable column → backfill existing rows → add NOT NULL/default constraint in a follow-up migration; ensure old app code (if still running during rollout) won't break on the interim schema.
  • Avoid: adding a NOT NULL column with no default directly to a populated table in one step; assuming zero rows exist without checking.
  • Verify: run migration against a copy of real/realistic data; confirm existing records end up valid; confirm old and new app versions both function during the compatibility window if rolling deploys are used.

F. Repeated request after uncertain network outcome

  • Inspect: whether the operation is naturally idempotent (e.g., PUT with full resource state) or not (e.g., POST /charge, POST /orders).
  • Decisions: for non-idempotent operations exposed to client retry, require/generate an idempotency key (stored with a unique constraint) and return the original result on duplicate submission instead of re-executing.
  • Avoid: blind automatic retries on POST/non-idempotent endpoints; relying on "it probably won't happen twice."
  • Verify: submit the same request twice with the same idempotency key and confirm only one side effect (e.g., one charge, one row) occurs.

G. List endpoint with pagination/limits

  • Inspect: expected data volume, existing pagination conventions in the codebase (cursor vs. offset, query param names).
  • Decisions: apply a sane default and maximum page size; use cursor-based pagination for large/frequently-changing datasets, offset pagination otherwise; add filtering only as justified by actual use case.
  • Avoid: unbounded SELECT * on a table that can grow large; reinventing pagination style inconsistent with the rest of the API.
  • Verify: test with page-size boundary values (0, max+1, negative), confirm response includes pagination metadata consistent with project convention.
  • Match the project's existing style/framework idioms rather than introducing new patterns or libraries.
  • Prefer DB constraints over "we'll validate it in the app" for anything that must never be violated.
  • Make illegal states unrepresentable in the data model where feasible, rather than patching with extra validation.
  • Keep error responses consistent and documented; distinguish client errors (4xx) from server errors (5xx) precisely.
  • Treat every externally-reachable write path as a target for malformed/malicious input, regardless of whether a frontend "already validates."
  • Trusting a resource ID in the request as proof of ownership.
  • Validating only on the frontend and skipping backend validation "because the UI already checks."
  • Using application-level checks alone for uniqueness/foreign-key integrity instead of DB constraints.
  • Running multi-step writes outside a transaction and assuming failures "won't happen."
  • Dropping columns/tables or adding NOT NULL constraints on populated tables without a staged plan.
  • Retrying non-idempotent operations (payments, order creation) automatically after timeouts.
  • Returning raw stack traces, SQL errors, or internal object structures to clients.
  • Adding pagination/versioning/zero-downtime migration ceremony to a small project where it adds no value — match effort to actual risk and scale.
0
Grade A-AI Skill Framework
Scorecard
Criteria Breakdown
Quick Start
13/15
Workflow
14/15
Examples
17/20
Completeness
19/20
Format
14/15
Conciseness
13/15