Nfr Enforcement Non Negotiable
Non-Functional Requirements (NFRs) are MUSTS for every feature touching user input, persistence, network, or shared state. They are not optional, not negotiable, and not 'skipped if not explicitly requested'. The right interpretation of scope-matched: refine WHICH NFR controls apply to a specific workload — NEVER skip NFR enforcement entirely.
Tags
Overview
Purpose
Non-Functional Requirements (NFRs) are MUSTS for every feature touching user input, persistence, network, or shared state. They are not optional, not negotiable, and not 'skipped if not explicitly requested'. The right interpretation of scope-matched: refine WHICH NFR controls apply to a specific workload — NEVER skip NFR enforcement entirely.
Rules
NFR-001: Input validation is a MUST at every system boundary — API request body, query params, URL params, form inputs, headers, cookies. No exception. No 'low-traffic so optional'. No 'admin-only so trust them'.
Never trust user input is the #1 web security principle. Admin users get compromised. Internal tools get exposed. Boundaries are where you validate, period.
Verification: Every handler / loader / form parses input through a typed schema. Failure produces structured 4xx with field-level errors. Live test: send malformed payload to every endpoint, verify rejection with clear error.
NFR-002: Authentication is verified at the START of every protected handler, before any business logic, before any DB query, before reading any param.
Auth checks after DB queries leak existence-of-resource via timing; auth checks after param parsing waste cycles on hostile traffic.
Verification: First non-import line of every protected handler is auth check. No exceptions.
NFR-003: Authorization (RBAC / ownership) is verified at the SAME layer as auth, before business logic. UI-only permission flags do NOT replace server-side checks.
Client-side gating is UX; server-side gating is security. The two coexist; neither replaces the other.
Verification: Every mutation route has `RequirePermission` (or equivalent) middleware. Every read route that exposes restricted data has the same.
NFR-004: SQL queries use parameterized placeholders ($1, $2, …) for ALL user input. String concatenation into SQL is forbidden, even for 'safe' values like integers or enums.
There is no such thing as a safe value at the SQL layer. Future refactors break safety assumptions. Parameterized queries are zero-cost.
Verification: Grep for `fmt.Sprintf` near `Query`/`Exec` calls — any user-input concatenation is a critical bug.
NFR-005: Rate limiting applies to every PUBLIC unauthenticated endpoint. Auth endpoints (login, magic link, reset) get stricter limits. Authenticated endpoints get rate limiting only when load measurement indicates abuse is plausible.
Public endpoints are the attack surface. Authenticated endpoints have accountability via the user identity.
Verification: Every route exposed to anonymous traffic has rate-limit middleware. Bypass via X-Requested-With or similar header is forbidden.
NFR-006: CSRF protection applies to every mutation endpoint (POST/PATCH/PUT/DELETE) that authenticates via cookies. API-token-only endpoints (Bearer header) are CSRF-exempt only when documented.
Cookie auth + state-changing methods = CSRF surface. The X-Requested-With + same-origin policy is one layer; explicit CSRF tokens are stronger.
Verification: Every cookie-auth mutation route has CSRF middleware or documented exemption.
NFR-007: Output encoding handles every user-supplied string in a way appropriate to its destination — Svelte template (auto-escape), JSON response (json.Marshal), SQL (parameterized), shell (forbidden without sanitizer), HTML attribute (encodeURIComponent).
XSS, injection, and similar attacks live in the gap between input trust and output context.
Verification: Grep for `{@html}` in Svelte — every instance is a documented exception. No raw shell exec with user input. No template-string SQL.
NFR-008: Error responses do not leak implementation details — stack traces, file paths, internal IDs, library versions. Errors map to user-facing messages; details go to structured logs.
Error responses are a recon vector for attackers and a UX failure for users.
Verification: Every handler error path returns a fixed-vocabulary message (auth-failed, not-found, validation-failed); logs carry the detail.
NFR-009: Logging records every auth event (login, logout, failed attempt, password reset, magic-link request), every authorization denial, every mutation by an authenticated actor. Logs are structured (slog), not formatted strings.
Audit trails are required by compliance, by incident response, and by your own future debugging.
Verification: Grep for `log.Printf` in handlers — every instance should become `slog.Info` with structured fields. Auth events have a dedicated audit logger.
NFR-010: Secrets (passwords, tokens, API keys, JWT secrets, DB passwords) never appear in source, in logs, in error messages, in commit history, or in URL query strings. Vault-injected only.
Once a secret hits a non-vault store, it must be rotated. Rotation is expensive. Prevention is free.
Verification: Pre-commit hook scans for high-entropy strings. `.env` files are gitignored. Logs sanitize known secret fields.
NFR-011: Timeouts apply at every boundary — HTTP request, DB query, external API call, file I/O. Defaults: 30s HTTP, 5s DB query, 10s external call. Long-running operations either return immediately with a job ID or stream progress.
Without timeouts, any slow path can exhaust the connection pool and DoS the service.
Verification: Every `context.Context` derived from a request has a deadline. Every external call uses `context.WithTimeout`.
NFR-012: Migrations are forward-only in production. Down migrations exist for dev but are not run in prod. Every migration is idempotent (`IF NOT EXISTS` / `IF EXISTS`) so partial application is recoverable.
Production rollback via down migration risks data loss; production rollback via forward fix preserves data.
Verification: Every migration has `CREATE … IF NOT EXISTS`, `DROP … IF EXISTS`. Tests run up + (up again) to confirm idempotency.
NFR-013: Performance budgets are stated upfront for any feature touching a hot path — read latency target, write latency target, max query count per request, max payload size. Indices, denormalization, and caching apply when budget would be missed.
Performance is a feature; it has a target and a verification.
Verification: Plan doc states budget. EXPLAIN ANALYZE confirms index usage. Load test sample for any net-new hot path.
NFR-014: Accessibility (WCAG 2.1 AA) applies to every Svelte page — semantic HTML, ARIA labels for interactive elements, keyboard navigation, focus indicators, 4.5:1 color contrast. No exception for 'internal admin pages' — admin staff includes people with disabilities.
Accessibility is not a checkbox; it's a baseline. Retrofit is harder than initial design.
Verification: Playwright tests for keyboard nav. Axe scan in CI. Manual screen-reader check on every new flow.
NFR-015: Test coverage targets are layer-specific: PD/domain > 90%, MD/repository > 80%, SI/handler > 70%, UI > 60%. Tests are TDD red-then-green. Tests cover happy path AND error/edge cases AND security boundaries.
Coverage targets prevent the slow drift of 'just one more uncovered branch'.
Verification: Coverage reports per package. Reviewer rejects PRs that lower coverage below target.