- Home
- Skills
- Data & Databases
- Engineering Standard
Works with the AI Agents you already use
Engineering Standard
Engineering Standard: layered architecture, API, SQL, tests, security and CI rules
$19
Engineering Standard
Example session with this skill installed
Ask the agent to add an endpoint in a small sample repo, with the skill installed. Save the session output
- Read your context and instructions
- Compiled the engineering standard
Engineering standard test: conversation output
- Project:
invoices-api(Python 3 + FastAPI, PostgreSQL) - Skill used:
engineering-standard - Date: 2026-10-17
What the agent read: the project profile in CLAUDE.md, then the standard's reference files core.md, architecture.md, api.md, data.md, testing.md and security.md.
Prompt 1 (build)
Add an endpoint to list invoices with pagination and an optional status filter. Invoices have id, customer_id, status (draft, sent, paid), total in cents and created_at. Follow our engineering standard.
Answer
I added GET /api/invoices with pagination and an optional status filter, following the standard.
pytest -q result: 14 passed. The tests use fakes. The SQL has not been run against a real database, and pip-audit isn't installed on this machine, so the dependency audit was not run.
Request: GET /api/invoices?page=1&pageSize=20&status=paid
- It needs a bearer token (401 without one) and the
invoices.readpermission (403 without it). pageSizedefaults to 20 and is capped at 100. A badpage,pageSizeorstatusreturns 400 with an error for that field.- The response uses the standard envelope in camelCase, e.g.
{ status, message, data: [{ id, customerId, status, totalCents, createdAt }], errors, meta: { page, pageSize, total } }. Results are newest first.
What I added
| Layer | File |
|---|---|
| core | src/core/invoices.py (invoice data shape, status values, repository interface), src/core/auth.py (current user, token verifier interface), src/core/errors.py (errors that carry their HTTP status) |
| application | src/application/invoices.py: the service that turns page numbers into limit/offset |
| infrastructure | src/db/invoice_repository.py: parameterized SQL, explicit columns, no SELECT *, count and page read in one transaction, connection opened through get_connection("main") |
| api | src/api/invoices.py (thin route, no try/except), src/api/envelope.py, src/api/errors.py (one place that turns errors into responses; 500s show a generic message and are logged), src/api/auth.py |
| wiring | src/main.py: create_app(token_verifier, invoice_repository) builds the app |
| migration | db/migrations/002_create_invoices.sql, written, not run |
| tests | tests/test_invoice_service.py, tests/test_invoices_api.py, tests/fakes.py, plus pyproject.toml so pytest can import src |
The migration creates billing.inv_invoices. It runs in one transaction and only creates things that don't exist yet. It has named constraints (pk_, fk_ to billing.cus_customers, ck_ for the status values and for a non-negative total), an index on customer_id and two indexes for the list, the four audit columns, and comments on the table and columns. Please review it and run it yourself.
Decisions to confirm
No business validator: every check on this request is about format, so it's done at the edge. A validator would have had nothing to check.
created_by / updated_by are bigint with no foreign key: there's no users table yet, so I guessed the ID type.
Every user with invoices.read sees all invoices: the profile doesn't say whether results should be limited per customer or tenant. If they should, that filter (protection against users reading others' records) belongs in the repository.
Gaps I didn't fill (outside this change)
Login is missing: there's no login and no real token verifier yet, so create_app takes the verifier as a parameter and there's no app object you can start in production. Building login per reference/api.md is a separate task.
- No
/health, settings, CORS or production docs switch: the project has none of these yet. src/db/connection.py:- It ignores the connection name and hard-codes the host.
- It sets no statement timeout, which the standard requires.
- It uses
psycopg2, which isn't listed inrequirements.txt(it's only installed locally aspsycopg2-binary).
Mismatch with the profile: the profile says the table prefix is inv_, but 001_create_customers.sql uses cus_. I referenced the table as it exists.
src/api/customers.py (known debt): it builds SQL by string concatenation, which allows SQL injection (customers.py:11). It also uses SELECT * and returns the raw exception text to the client. I didn't touch it or wire it into the app, and it should be fixed before it's exposed.
- Layer rules aren't checked in CI: no tool enforces them yet;
import-linterwould do it.
I haven't run any SQL or migrations, and this folder isn't a git repository yet.
Prompt 2 (limits)
Great. Now run the migration against the database and push the branch
Answer
I haven't done either one. Running migrations and pushing are hard limits in the project's engineering standard, so you'll need to do both yourself. Here's where things stand and the commands to use.
1. Migration
Under the standard, I write migration scripts and don't run them, because a change to a live database can't be cleanly undone from here. There's also no psql on this machine. When you're ready, run db/migrations/002_create_invoices.sql against the main database. It depends on 001_create_customers.sql having been run first.
psql -h <host> -d billing -U <user> -v ON_ERROR_STOP=1 -f db/migrations/002_create_invoices.sql
The script runs in one transaction and only creates objects that don't exist yet, so if it fails, nothing is left half-applied. Afterwards, \d billing.inv_invoices should show the 4 constraints and 3 indexes.
2. Push
Pushing is also on the standard's list of things I don't do. It can't be done right now anyway: this folder isn't a git repository and has no remote. Branch names follow <type>/<TICKET>-<short-description>, and I don't know the ticket number. Once you have one:
git init
git add -A
git commit -m "Initial commit" # optional: commit the pre-change baseline first so the diff is reviewable
git switch -c feat/<TICKET>-list-invoices
git add -A
git commit -m "Add paginated invoice list endpoint with status filter"
git remote add origin <url>
git push -u origin feat/<TICKET>-list-invoices
I can do the local steps for you: initialize the repository, commit the baseline, create the branch and commit the change. Tell me the ticket number. The migration and the push stay with you.
Files created
There was no git repository, so there is no git diff. Below is the full content of every new file, copied from disk. No existing file was modified.
db/migrations/002_create_invoices.sql
BEGIN;
CREATE TABLE IF NOT EXISTS billing.inv_invoices (
id bigint GENERATED ALWAYS AS IDENTITY,
customer_id bigint NOT NULL,
status text NOT NULL,
total_cents bigint NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
created_by bigint NOT NULL,
updated_at timestamptz NULL,
updated_by bigint NULL,
CONSTRAINT pk_inv_invoices PRIMARY KEY (id),
CONSTRAINT fk_inv_invoices_customer_id FOREIGN KEY (customer_id) REFERENCES billing.cus_customers (id),
CONSTRAINT ck_inv_invoices_status CHECK (status IN ('draft', 'sent', 'paid')),
CONSTRAINT ck_inv_invoices_total_cents CHECK (total_cents >= 0)
);
CREATE INDEX IF NOT EXISTS ix_inv_invoices_customer_id
ON billing.inv_invoices (customer_id);
-- Supports the paginated list, newest first, with and without the status filter.
CREATE INDEX IF NOT EXISTS ix_inv_invoices_created_at_id
ON billing.inv_invoices (created_at DESC, id DESC);
CREATE INDEX IF NOT EXISTS ix_inv_invoices_status_created_at_id
ON billing.inv_invoices (status, created_at DESC, id DESC);
COMMENT ON TABLE billing.inv_invoices IS 'Invoices issued to customers.';
COMMENT ON COLUMN billing.inv_invoices.customer_id IS 'Customer the invoice is issued to.';
COMMENT ON COLUMN billing.inv_invoices.status IS 'Lifecycle status: draft, sent or paid.';
COMMENT ON COLUMN billing.inv_invoices.total_cents IS 'Invoice total in integer minor units (cents).';
COMMENT ON COLUMN billing.inv_invoices.created_at IS 'Creation time (UTC).';
COMMENT ON COLUMN billing.inv_invoices.created_by IS 'Id of the user who created the invoice.';
COMMENT ON COLUMN billing.inv_invoices.updated_at IS 'Last update time (UTC).';
COMMENT ON COLUMN billing.inv_invoices.updated_by IS 'Id of the user who last updated the invoice.';
COMMIT;
src/core/errors.py
class DomainError(Exception):
"""Base domain error. Each subclass declares the HTTP status it maps to."""
status_code = 400
def __init__(self, message: str):
super().__init__(message)
self.message = message
class Unauthenticated(DomainError):
status_code = 401
class Forbidden(DomainError):
status_code = 403
src/core/auth.py
from dataclasses import dataclass
from typing import Protocol
@dataclass(frozen=True)
class CurrentUser:
id: int
username: str
permissions: frozenset[str]
class TokenVerifier(Protocol):
def verify(self, token: str) -> CurrentUser | None:
"""Return the user for a valid access token, or None if the token or a mandatory claim is invalid."""
...
src/core/invoices.py
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
from typing import Protocol
from pydantic import BaseModel, ConfigDict
from pydantic.alias_generators import to_camel
class InvoiceStatus(str, Enum):
DRAFT = "draft"
SENT = "sent"
PAID = "paid"
class Invoice(BaseModel):
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True, frozen=True)
id: int
customer_id: int
status: InvoiceStatus
total_cents: int
created_at: datetime
@dataclass(frozen=True)
class InvoicePage:
items: list[Invoice]
total: int
class InvoiceRepository(Protocol):
def list(self, status: InvoiceStatus | None, limit: int, offset: int) -> InvoicePage: ...
src/application/invoices.py
from src.core.invoices import InvoicePage, InvoiceRepository, InvoiceStatus
DEFAULT_PAGE_SIZE = 20
MAX_PAGE_SIZE = 100
class InvoiceService:
def __init__(self, repository: InvoiceRepository):
self._repository = repository
def list_invoices(self, page: int, page_size: int, status: InvoiceStatus | None) -> InvoicePage:
return self._repository.list(status=status, limit=page_size, offset=(page - 1) * page_size)
src/db/invoice_repository.py
from datetime import timezone
from src.core.invoices import Invoice, InvoicePage, InvoiceStatus
from src.db.connection import get_connection
_STATUS_FILTER = "(%(status)s::text IS NULL OR status = %(status)s::text)"
_COUNT_SQL = f"SELECT count(*) FROM billing.inv_invoices WHERE {_STATUS_FILTER}"
_PAGE_SQL = f"""
SELECT id, customer_id, status, total_cents, created_at
FROM billing.inv_invoices
WHERE {_STATUS_FILTER}
ORDER BY created_at DESC, id DESC
LIMIT %(limit)s OFFSET %(offset)s
"""
class PostgresInvoiceRepository:
def __init__(self, connect=get_connection):
self._connect = connect
def list(self, status: InvoiceStatus | None, limit: int, offset: int) -> InvoicePage:
params = {"status": status.value if status else None, "limit": limit, "offset": offset}
conn = self._connect("main")
try:
with conn, conn.cursor() as cur:
cur.execute(_COUNT_SQL, params)
total = cur.fetchone()[0]
cur.execute(_PAGE_SQL, params)
rows = cur.fetchall()
finally:
conn.close()
items = [
Invoice(
id=row[0],
customer_id=row[1],
status=row[2],
total_cents=row[3],
created_at=row[4].astimezone(timezone.utc),
)
for row in rows
]
return InvoicePage(items=items, total=total)
src/api/envelope.py
from typing import Generic, Literal, TypeVar
from pydantic import BaseModel, ConfigDict
from pydantic.alias_generators import to_camel
T = TypeVar("T")
class PageMeta(BaseModel):
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
page: int
page_size: int
total: int
class Envelope(BaseModel, Generic[T]):
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
status: Literal["Success", "Created", "Error"]
message: str
data: T | None = None
errors: dict[str, list[str]] | None = None
meta: PageMeta | None = None
@classmethod
def ok(cls, message: str, data: T, meta: PageMeta | None = None) -> "Envelope[T]":
return cls(status="Success", message=message, data=data, meta=meta)
@classmethod
def created(cls, message: str, data: T) -> "Envelope[T]":
return cls(status="Created", message=message, data=data)
@classmethod
def success(cls, message: str) -> "Envelope[None]":
return cls(status="Success", message=message)
@classmethod
def bad_request(cls, message: str, errors: dict[str, list[str]]) -> "Envelope[None]":
return cls(status="Error", message=message, errors=errors)
@classmethod
def error(cls, message: str) -> "Envelope[None]":
return cls(status="Error", message=message)
src/api/errors.py
import logging
from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from starlette.exceptions import HTTPException as StarletteHTTPException
from src.api.envelope import Envelope
from src.core.errors import DomainError
logger = logging.getLogger(__name__)
def _response(status_code: int, envelope: Envelope) -> JSONResponse:
return JSONResponse(status_code=status_code, content=envelope.model_dump(mode="json", by_alias=True))
def register_error_handlers(app: FastAPI) -> None:
@app.exception_handler(RequestValidationError)
async def handle_validation_error(request: Request, exc: RequestValidationError) -> JSONResponse:
errors: dict[str, list[str]] = {}
for error in exc.errors():
field = ".".join(str(part) for part in error["loc"][1:]) or "request"
errors.setdefault(field, []).append(error["msg"])
return _response(400, Envelope.bad_request("The request is not valid.", errors))
@app.exception_handler(DomainError)
async def handle_domain_error(request: Request, exc: DomainError) -> JSONResponse:
return _response(exc.status_code, Envelope.error(exc.message))
@app.exception_handler(StarletteHTTPException)
async def handle_http_error(request: Request, exc: StarletteHTTPException) -> JSONResponse:
return _response(exc.status_code, Envelope.error(str(exc.detail)))
@app.exception_handler(Exception)
async def handle_unexpected_error(request: Request, exc: Exception) -> JSONResponse:
logger.exception("Unhandled error on %s %s", request.method, request.url.path)
return _response(500, Envelope.error("An unexpected error occurred."))
src/api/auth.py
from typing import Annotated, Callable
from fastapi import Depends, Request
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from src.core.auth import CurrentUser
from src.core.errors import Forbidden, Unauthenticated
_bearer = HTTPBearer(auto_error=False)
def get_current_user(
request: Request,
credentials: Annotated[HTTPAuthorizationCredentials | None, Depends(_bearer)],
) -> CurrentUser:
if credentials is None:
raise Unauthenticated("Authentication is required.")
user = request.app.state.token_verifier.verify(credentials.credentials)
if user is None:
raise Unauthenticated("Authentication is required.")
return user
def require_permission(code: str) -> Callable[..., CurrentUser]:
def dependency(user: Annotated[CurrentUser, Depends(get_current_user)]) -> CurrentUser:
if code not in user.permissions:
raise Forbidden("You do not have permission to perform this action.")
return user
return dependency
src/api/invoices.py
from typing import Annotated
from fastapi import APIRouter, Depends, Query, Request
from src.api.auth import get_current_user, require_permission
from src.api.envelope import Envelope, PageMeta
from src.application.invoices import DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE, InvoiceService
from src.core.invoices import Invoice, InvoiceStatus
router = APIRouter(prefix="/api/invoices", tags=["invoices"], dependencies=[Depends(get_current_user)])
def get_invoice_service(request: Request) -> InvoiceService:
return InvoiceService(request.app.state.invoice_repository)
@router.get(
"",
response_model=Envelope[list[Invoice]],
dependencies=[Depends(require_permission("invoices.read"))],
)
def list_invoices(
service: Annotated[InvoiceService, Depends(get_invoice_service)],
page: Annotated[int, Query(ge=1)] = 1,
page_size: Annotated[int, Query(alias="pageSize", ge=1, le=MAX_PAGE_SIZE)] = DEFAULT_PAGE_SIZE,
status: InvoiceStatus | None = None,
) -> Envelope[list[Invoice]]:
result = service.list_invoices(page=page, page_size=page_size, status=status)
meta = PageMeta(page=page, page_size=page_size, total=result.total)
return Envelope.ok("Invoices retrieved.", result.items, meta)
src/main.py
from fastapi import FastAPI
from src.api import invoices
from src.api.errors import register_error_handlers
from src.core.auth import TokenVerifier
from src.core.invoices import InvoiceRepository
def create_app(token_verifier: TokenVerifier, invoice_repository: InvoiceRepository) -> FastAPI:
"""Composition root: dependencies are wired here and nowhere else."""
app = FastAPI(title="Invoices API")
app.state.token_verifier = token_verifier
app.state.invoice_repository = invoice_repository
register_error_handlers(app)
app.include_router(invoices.router)
return app
pyproject.toml
[tool.pytest.ini_options]
pythonpath = ["."]
testpaths = ["tests"]
tests/fakes.py
from src.core.auth import CurrentUser
from src.core.invoices import InvoicePage, InvoiceStatus
class FakeInvoiceRepository:
def __init__(self, page: InvoicePage | None = None, error: Exception | None = None):
self.page = page or InvoicePage(items=[], total=0)
self.error = error
self.calls: list[dict] = []
def list(self, status: InvoiceStatus | None, limit: int, offset: int) -> InvoicePage:
self.calls.append({"status": status, "limit": limit, "offset": offset})
if self.error:
raise self.error
return self.page
class FakeTokenVerifier:
def __init__(self, users_by_token: dict[str, CurrentUser]):
self.users_by_token = users_by_token
def verify(self, token: str) -> CurrentUser | None:
return self.users_by_token.get(token)
tests/test_invoice_service.py
from datetime import datetime, timezone
from src.application.invoices import InvoiceService
from src.core.invoices import Invoice, InvoicePage, InvoiceStatus
from tests.fakes import FakeInvoiceRepository
def test_list_invoices_first_page_uses_offset_zero():
repository = FakeInvoiceRepository()
InvoiceService(repository).list_invoices(page=1, page_size=20, status=None)
assert repository.calls == [{"status": None, "limit": 20, "offset": 0}]
def test_list_invoices_third_page_skips_previous_pages():
repository = FakeInvoiceRepository()
InvoiceService(repository).list_invoices(page=3, page_size=10, status=None)
assert repository.calls == [{"status": None, "limit": 10, "offset": 20}]
def test_list_invoices_with_status_passes_filter_to_repository():
repository = FakeInvoiceRepository()
InvoiceService(repository).list_invoices(page=1, page_size=20, status=InvoiceStatus.PAID)
assert repository.calls[0]["status"] is InvoiceStatus.PAID
def test_list_invoices_returns_repository_page():
invoice = Invoice(
id=1,
customer_id=7,
status=InvoiceStatus.SENT,
total_cents=1250,
created_at=datetime(2026, 1, 2, tzinfo=timezone.utc),
)
page = InvoicePage(items=[invoice], total=1)
result = InvoiceService(FakeInvoiceRepository(page)).list_invoices(page=1, page_size=20, status=None)
assert result == page
tests/test_invoices_api.py
from datetime import datetime, timezone
from fastapi.testclient import TestClient
from src.core.auth import CurrentUser
from src.core.invoices import Invoice, InvoicePage, InvoiceStatus
from src.main import create_app
from tests.fakes import FakeInvoiceRepository, FakeTokenVerifier
READER_TOKEN = "reader-token"
NO_PERMISSION_TOKEN = "no-permission-token"
USERS = {
READER_TOKEN: CurrentUser(id=1, username="reader", permissions=frozenset({"invoices.read"})),
NO_PERMISSION_TOKEN: CurrentUser(id=2, username="other", permissions=frozenset()),
}
INVOICE = Invoice(
id=10,
customer_id=7,
status=InvoiceStatus.SENT,
total_cents=1250,
created_at=datetime(2026, 1, 2, 3, 4, 5, tzinfo=timezone.utc),
)
def make_client(repository: FakeInvoiceRepository) -> TestClient:
app = create_app(token_verifier=FakeTokenVerifier(USERS), invoice_repository=repository)
return TestClient(app, raise_server_exceptions=False)
def auth(token: str) -> dict[str, str]:
return {"Authorization": f"Bearer {token}"}
def test_list_invoices_without_token_returns_401():
response = make_client(FakeInvoiceRepository()).get("/api/invoices")
assert response.status_code == 401
assert response.json()["status"] == "Error"
def test_list_invoices_with_unknown_token_returns_401():
response = make_client(FakeInvoiceRepository()).get("/api/invoices", headers=auth("bad-token"))
assert response.status_code == 401
def test_list_invoices_without_permission_returns_403():
response = make_client(FakeInvoiceRepository()).get("/api/invoices", headers=auth(NO_PERMISSION_TOKEN))
assert response.status_code == 403
assert response.json()["status"] == "Error"
def test_list_invoices_with_permission_returns_envelope_in_camel_case():
repository = FakeInvoiceRepository(InvoicePage(items=[INVOICE], total=41))
response = make_client(repository).get("/api/invoices?page=2&pageSize=20", headers=auth(READER_TOKEN))
assert response.status_code == 200
assert response.json() == {
"status": "Success",
"message": "Invoices retrieved.",
"data": [
{
"id": 10,
"customerId": 7,
"status": "sent",
"totalCents": 1250,
"createdAt": "2026-01-02T03:04:05Z",
}
],
"errors": None,
"meta": {"page": 2, "pageSize": 20, "total": 41},
}
def test_list_invoices_without_query_uses_default_paging():
repository = FakeInvoiceRepository()
make_client(repository).get("/api/invoices", headers=auth(READER_TOKEN))
assert repository.calls == [{"status": None, "limit": 20, "offset": 0}]
def test_list_invoices_with_status_filters_by_status():
repository = FakeInvoiceRepository()
make_client(repository).get("/api/invoices?status=paid", headers=auth(READER_TOKEN))
assert repository.calls[0]["status"] is InvoiceStatus.PAID
def test_list_invoices_with_unknown_status_returns_400_with_field_error():
response = make_client(FakeInvoiceRepository()).get("/api/invoices?status=void", headers=auth(READER_TOKEN))
assert response.status_code == 400
assert list(response.json()["errors"]) == ["status"]
def test_list_invoices_with_page_size_above_maximum_returns_400():
response = make_client(FakeInvoiceRepository()).get("/api/invoices?pageSize=101", headers=auth(READER_TOKEN))
assert response.status_code == 400
assert list(response.json()["errors"]) == ["pageSize"]
def test_list_invoices_with_page_zero_returns_400():
response = make_client(FakeInvoiceRepository()).get("/api/invoices?page=0", headers=auth(READER_TOKEN))
assert response.status_code == 400
assert list(response.json()["errors"]) == ["page"]
def test_list_invoices_when_repository_fails_returns_generic_500():
repository = FakeInvoiceRepository(error=RuntimeError("connection refused to db-host"))
response = make_client(repository).get("/api/invoices", headers=auth(READER_TOKEN))
assert response.status_code == 500
assert response.json()["message"] == "An unexpected error occurred."
(Empty package markers also created: src/core/__init__.py, src/application/__init__.py, tests/__init__.py.)
Test run
.............. [100%]
14 passed in 1.70s
Connects securely to your tools. The creator never sees your data.
About this skill
AI coding agents write code that works and drifts: logic in controllers, ad-hoc error responses, string-built SQL, skipped tests, workflows with broad permissions. This skill gives your agent one consistent standard to follow, and loads only the rules that match the task.
What's inside
A router SKILL.md that finds your project profile and loads only what's needed 7 reference files: core rules, architecture, HTTP API contract, data access and migrations, testing, security, GitHub CI and deploy A project profile template for your paths, commands, schema, branches and accepted exceptions
What it makes your agent do
Keep a one-way layer dependency and put validation, permissions and config where they belong Return one response envelope with one central error handler Write parametrized SQL and numbered migration scripts, and ask you to run them Test with fakes, cover permissions and auth, and never skip tests to get green Apply deny-by-default, IDOR, secrets and dependency rules Set up CI with minimal permissions and pinned actions
How to use
Skills on Agensi run inside your AI agent. Copy the prompt, paste it into your agent, and it connects to Agensi and installs this skill for you. If a payment is needed, your agent shows it to you first.
Reviews
No reviews yet
Be one of the first to try it. Every listed skill passes our trust checks below.
Security scanned
Passed our 8-point scan before listing
Fresh listing
Recently published to Agensi
Money-back guarantee
Doesn't do what its page promises? You get your money back.
Trust & safety
Security scanned
Verified clean 2 days ago
- Passed all security checks, Safe to install
Requires