{
 "schema_version": 5,
 "kind": "stable",
 "name": "Good Code Review",
 "url": "https://goodcodereview.com/",
 "licence": "https://creativecommons.org/licenses/by/4.0/",
 "generated": "2026-11-06",
 "concerns": [
  "correctness",
  "security",
  "performance",
  "reliability",
  "compatibility",
  "testing",
  "maintainability",
  "accessibility",
  "usability",
  "observability",
  "process"
 ],
 "levels": [
  {
   "id": "change",
   "name": "Change review",
   "input": "diff / PR",
   "description": "Checks a diff or pull request: scope, size, compatibility, tests, and the description.",
   "stable_count": 27,
   "draft_count": 14,
   "gives_verdict": true,
   "url": "https://goodcodereview.com/change",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "function",
   "name": "Function audit",
   "input": "file / function",
   "description": "Checks one file or function: names, errors, concurrency, and security at the call level.",
   "stable_count": 62,
   "draft_count": 8,
   "gives_verdict": true,
   "url": "https://goodcodereview.com/function",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "module",
   "name": "Module audit",
   "input": "package / directory",
   "description": "Checks a package or path: boundaries, state, dependencies, and the public surface.",
   "stable_count": 24,
   "draft_count": 11,
   "gives_verdict": true,
   "url": "https://goodcodereview.com/module",
   "markdown_url": "https://goodcodereview.com/module.md"
  },
  {
   "id": "service",
   "name": "Service audit",
   "input": "service",
   "description": "Checks one running service: its API, failure handling, limits, and security.",
   "stable_count": 59,
   "draft_count": 8,
   "gives_verdict": true,
   "url": "https://goodcodereview.com/service",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "system",
   "name": "System audit",
   "input": "cross-service design doc",
   "description": "Checks a design that spans services: shared data, caches, migrations, and failure modes.",
   "stable_count": 16,
   "draft_count": 4,
   "gives_verdict": true,
   "url": "https://goodcodereview.com/system",
   "markdown_url": "https://goodcodereview.com/system.md"
  },
  {
   "id": "codebase",
   "name": "Codebase audit",
   "input": "repo",
   "description": "Checks a whole repository: CI gates, dependencies, setup, and conventions.",
   "stable_count": 21,
   "draft_count": 3,
   "gives_verdict": true,
   "url": "https://goodcodereview.com/codebase",
   "markdown_url": "https://goodcodereview.com/codebase.md"
  },
  {
   "id": "production",
   "name": "Production audit",
   "input": "release / launch",
   "description": "Checks a release or launch: rollout, rollback, alerts, and load.",
   "stable_count": 9,
   "draft_count": 1,
   "gives_verdict": true,
   "url": "https://goodcodereview.com/production",
   "markdown_url": "https://goodcodereview.com/production.md"
  },
  {
   "id": "incident",
   "name": "Incident audit",
   "input": "incident",
   "description": "Checks an incident and its follow-up: evidence, guards, and owned actions.",
   "stable_count": 2,
   "draft_count": 3,
   "gives_verdict": true,
   "url": "https://goodcodereview.com/incident",
   "markdown_url": "https://goodcodereview.com/incident.md"
  }
 ],
 "cards": [
  {
   "id": "code-a11y-check-in-ci",
   "level": "codebase",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "accessibility",
    "testing"
   ],
   "topics": [
    "frontend",
    "ci"
   ],
   "lang": "core",
   "applies_when": "The repo builds a web UI and has an automated test suite.",
   "context": [
    "ui"
   ],
   "tool_gate": "This card is the gate: axe (through `@axe-core/playwright` or similar) against rendered pages in the test step; `eslint-plugin-jsx-a11y` in the lint step for JSX code.",
   "note": "Rule backed by USWDS (axe and pa11y on every change, failing merges blocked), Microsoft Playwright (the test verifies there are no violations), and Deque axe-core (axe calls in existing functional tests, example throws on violations); the JSX lint (jsx-eslint only) and merge blocking in CI (USWDS only) sit in Why; GDS found the best of 13 automated tools caught 40% of planted barriers",
   "lacks": null,
   "rule": "Run an automated accessibility check, such as axe on rendered pages, in the automated tests, and fail the test on a violation.",
   "why": "A violation that nobody sees at review ships. A failing test catches the missing label or `alt` on the pull request that adds it, and run in CI on every change it blocks the merge. A JSX lint such as `jsx-a11y` catches some of the same problems earlier, in the editor. Either way it is a floor: tools find only part of the problems, so a person still tests with a keyboard.",
   "counterpoint": "\"Tools only catch a minority, and we have a backlog, so a failing check would turn every build red.\" The check catches the cheap failures at no review cost, which frees the manual pass for what only a person can judge. For the backlog, exclude the known violations by name (`disableRules`, `exclude`, or a fingerprint of each known issue) and fail on anything new. Remove each exclusion once it is fixed.",
   "fail": {
    "language": "js",
    "code": "// e2e: axe runs, but nothing fails on what it finds\nconst results = await new AxeBuilder({ page }).analyze();\nconsole.log(results.violations.length);"
   },
   "pass": {
    "language": "js",
    "code": "// e2e: fail on any axe violation\nconst results = await new AxeBuilder({ page }).analyze();\nexpect(results.violations).toEqual([]);"
   },
   "sources": [
    "https://github.com/jsx-eslint/eslint-plugin-jsx-a11y",
    "https://www.deque.com/axe/core-documentation/",
    "https://alphagov.github.io/accessibility-tool-audit/",
    "https://www.gov.uk/service-manual/helping-people-to-use-your-service/making-your-service-accessible-an-introduction",
    "https://designsystem.digital.gov/documentation/accessibility/",
    "https://www.gov.uk/service-manual/helping-people-to-use-your-service/testing-for-accessibility",
    "https://playwright.dev/docs/accessibility-testing",
    "https://github.com/dequelabs/axe-core",
    "https://accessibility.civicactions.com/posts/automated-accessibility-testing-leveraging-github-actions-and-pa11y-ci-with-axe",
    "https://accessibility-manual.dwp.gov.uk/best-practice/automated-testing-using-axe-core-and-pa11y",
    "https://accessibility.arizona.edu/web-apps/cicd-testing"
   ],
   "orgs": [
    {
     "id": "jsx-eslint",
     "name": "jsx-eslint project",
     "sources": [
      "https://github.com/jsx-eslint/eslint-plugin-jsx-a11y"
     ],
     "strength": "unjudged"
    },
    {
     "id": "deque",
     "name": "Deque",
     "sources": [
      "https://www.deque.com/axe/core-documentation/",
      "https://github.com/dequelabs/axe-core"
     ],
     "strength": "unjudged"
    },
    {
     "id": "govuk",
     "name": "GOV.UK",
     "sources": [
      "https://alphagov.github.io/accessibility-tool-audit/",
      "https://www.gov.uk/service-manual/helping-people-to-use-your-service/making-your-service-accessible-an-introduction",
      "https://www.gov.uk/service-manual/helping-people-to-use-your-service/testing-for-accessibility",
      "https://accessibility-manual.dwp.gov.uk/best-practice/automated-testing-using-axe-core-and-pa11y"
     ],
     "strength": "supports"
    },
    {
     "id": "uswds",
     "name": "USWDS",
     "sources": [
      "https://designsystem.digital.gov/documentation/accessibility/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://playwright.dev/docs/accessibility-testing"
     ],
     "strength": "unjudged"
    },
    {
     "id": "civicactions",
     "name": "CivicActions",
     "sources": [
      "https://accessibility.civicactions.com/posts/automated-accessibility-testing-leveraging-github-actions-and-pa11y-ci-with-axe"
     ],
     "strength": "supports"
    },
    {
     "id": "arizona",
     "name": "University of Arizona",
     "sources": [
      "https://accessibility.arizona.edu/web-apps/cicd-testing"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "strong",
   "url": "https://goodcodereview.com/codebase#code-a11y-check-in-ci",
   "markdown_url": "https://goodcodereview.com/codebase.md"
  },
  {
   "id": "code-adr",
   "level": "codebase",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "maintainability",
    "process"
   ],
   "topics": [
    "documentation",
    "decisions"
   ],
   "lang": "core",
   "applies_when": "Auditing whether the reasons behind significant design choices are recorded.",
   "context": [],
   "tool_gate": null,
   "note": null,
   "lacks": null,
   "rule": "Keep a short written record for each significant design choice (datastore, framework, deviation from a pattern) that states the context, the decision, and its consequences.",
   "why": "Without a record, the next reader reverses a decision blindly or cargo-cults it. Also list the options you ruled out and why: a record that weighs only the chosen design, its strengths or its flaws, is not a trade-off analysis.",
   "counterpoint": "ADRs rot and become paperwork. Keep each to a page, write them only for hard-to-reverse choices, and mark superseded ones.",
   "fail": {
    "language": "text",
    "code": "Repo uses Kafka for one 10-msg/day job. No record why; nobody dares remove it."
   },
   "pass": {
    "language": "text",
    "code": "docs/adr/0007-queue.md: context, decision, consequences, status, and the 2 options ruled out."
   },
   "sources": [
    "https://docs.aws.amazon.com/prescriptive-guidance/latest/architectural-decision-records/adr-process.html",
    "https://learn.microsoft.com/en-us/azure/well-architected/architect-role/architecture-decision-record",
    "https://cloud.google.com/architecture/architecture-decision-records",
    "https://www.gov.uk/government/publications/architectural-decision-record-framework/architectural-decision-record-framework",
    "https://engineering.atspotify.com/2020/04/when-should-i-write-an-architecture-decision-record",
    "https://www.cognitect.com/blog/2011/11/15/documenting-architecture-decisions",
    "https://github.com/matthiasn/talk-transcripts/blob/master/Hickey_Rich/HammockDrivenDev.md",
    "https://github.com/rust-lang/rfcs/blob/master/0000-template.md",
    "https://peps.python.org/pep-0012/",
    "https://github.com/kubernetes/enhancements/blob/master/keps/NNNN-kep-template/README.md",
    "https://rfd.shared.oxide.computer/rfd/0001"
   ],
   "orgs": [
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://docs.aws.amazon.com/prescriptive-guidance/latest/architectural-decision-records/adr-process.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/well-architected/architect-role/architecture-decision-record"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://cloud.google.com/architecture/architecture-decision-records"
     ],
     "strength": "unjudged"
    },
    {
     "id": "govuk",
     "name": "GOV.UK",
     "sources": [
      "https://www.gov.uk/government/publications/architectural-decision-record-framework/architectural-decision-record-framework"
     ],
     "strength": "unjudged"
    },
    {
     "id": "spotify",
     "name": "Spotify",
     "sources": [
      "https://engineering.atspotify.com/2020/04/when-should-i-write-an-architecture-decision-record"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cognitect",
     "name": "Cognitect",
     "sources": [
      "https://www.cognitect.com/blog/2011/11/15/documenting-architecture-decisions"
     ],
     "strength": "unjudged"
    },
    {
     "id": "rust",
     "name": "Rust project",
     "sources": [
      "https://github.com/rust-lang/rfcs/blob/master/0000-template.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://peps.python.org/pep-0012/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "kubernetes",
     "name": "Kubernetes",
     "sources": [
      "https://github.com/kubernetes/enhancements/blob/master/keps/NNNN-kep-template/README.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "oxide",
     "name": "Oxide Computer Company",
     "sources": [
      "https://rfd.shared.oxide.computer/rfd/0001"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "hickey",
     "name": "Rich Hickey",
     "sources": [
      "https://github.com/matthiasn/talk-transcripts/blob/master/Hickey_Rich/HammockDrivenDev.md"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/codebase#code-adr",
   "markdown_url": "https://goodcodereview.com/codebase.md"
  },
  {
   "id": "code-build-hermetic",
   "level": "codebase",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "process"
   ],
   "topics": [
    "build",
    "reproducibility"
   ],
   "lang": "core",
   "applies_when": "The repository has a build, a packaging, or a release step.",
   "context": [],
   "tool_gate": "Build twice in clean containers and compare checksums; `diffoscope` shows where two artifacts differ.",
   "note": "Rule backed by Google (Bazel hermeticity), Airbnb (Gradle tasks could touch the whole file system, Bazel sandboxes each action to its declared inputs) and Slack (a build step must use only the inputs it is given and give the same output for them); Dropbox lists hermetic, reproducible builds as a Bazel benefit; pinned toolchains (Google only) sit in Why",
   "lacks": null,
   "rule": "Build from declared inputs only, so that the same source revision gives the same artifact.",
   "why": "A build that reads whatever is installed on the machine gives different output on different machines, so a bug cannot be reproduced and a release cannot be checked against its source. Tools are inputs too: pin the compiler and toolchain versions, and do not call host binaries. Undeclared shared files also break builds at scale, for example two tasks that clean and use the same `/tmp` directory race each other.",
   "counterpoint": "\"Bit-identical builds are overkill.\" Full reproducibility is the ceiling. Declared inputs, pinned tools, and a recorded revision are the floor, and they cost little. [[code-dependency-pinning]] covers dependency pins.",
   "fail": {
    "language": "yaml",
    "code": "steps:\n  - run: make               # uses whatever gcc, node and protoc the runner has\n  - run: date > BUILD_ID    # the clock is an undeclared input"
   },
   "pass": {
    "language": "yaml",
    "code": "container:\n  image: ghcr.io/acme/builder@sha256:4b1e...   # toolchain pinned by digest\nsteps:\n  - run: SOURCE_DATE_EPOCH=$(git log -1 --format=%ct) make   # timestamps come from the commit"
   },
   "sources": [
    "https://bazel.build/basics/hermeticity",
    "https://reproducible-builds.org/docs/definition/",
    "https://airbnb.tech/infrastructure/migrating-airbnbs-jvm-monorepo-to-bazel/",
    "https://slack.engineering/build-better-software-to-build-software-better/",
    "https://dropbox.tech/infrastructure/how-we-migrated-dropbox-from-nginx-to-envoy"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://bazel.build/basics/hermeticity"
     ],
     "strength": "unjudged"
    },
    {
     "id": "repro-builds",
     "name": "Reproducible Builds",
     "sources": [
      "https://reproducible-builds.org/docs/definition/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "airbnb",
     "name": "Airbnb",
     "sources": [
      "https://airbnb.tech/infrastructure/migrating-airbnbs-jvm-monorepo-to-bazel/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "slack",
     "name": "Slack",
     "sources": [
      "https://slack.engineering/build-better-software-to-build-software-better/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "dropbox",
     "name": "Dropbox",
     "sources": [
      "https://dropbox.tech/infrastructure/how-we-migrated-dropbox-from-nginx-to-envoy"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/codebase#code-build-hermetic",
   "markdown_url": "https://goodcodereview.com/codebase.md"
  },
  {
   "id": "code-ci-gates",
   "level": "codebase",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "process"
   ],
   "topics": [
    "ci",
    "quality-gates"
   ],
   "lang": "core",
   "applies_when": "Auditing which checks must pass before a change merges.",
   "context": [],
   "tool_gate": "Branch protection with required status checks.",
   "note": null,
   "lacks": null,
   "rule": "Require review and passing build, tests, lint, and type checks on the default branch before merge, and leave nothing mechanical to reviewer vigilance.",
   "why": "Any rule that depends on someone remembering will be skipped. Components where changes merge unreviewed or without discussion tend to ship more post-release defects.",
   "counterpoint": "Gates slow small teams, and flaky tests train people to override. Keep gates few and reliable, fix flakes instead of overriding them, and let the gates own the mechanical checks so review time goes to design.",
   "fail": {
    "language": "text",
    "code": "Default branch: no protection. Force-push allowed. CI optional."
   },
   "pass": {
    "language": "text",
    "code": "Protected main: 1 approval + required checks (build, test, lint, types)."
   },
   "sources": [
    "https://github.com/ossf/scorecard/blob/main/docs/checks.md",
    "https://blog.palantir.com/code-review-best-practices-19e02780015f",
    "https://www.kubernetes.dev/docs/guide/pull-requests/",
    "https://rebels.cs.uwaterloo.ca/papers/msr2014_mcintosh.pdf"
   ],
   "orgs": [
    {
     "id": "openssf",
     "name": "OpenSSF",
     "sources": [
      "https://github.com/ossf/scorecard/blob/main/docs/checks.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "palantir",
     "name": "Palantir",
     "sources": [
      "https://blog.palantir.com/code-review-best-practices-19e02780015f"
     ],
     "strength": "unjudged"
    },
    {
     "id": "kubernetes",
     "name": "Kubernetes",
     "sources": [
      "https://www.kubernetes.dev/docs/guide/pull-requests/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "mcintosh",
     "name": "McIntosh et al.",
     "sources": [
      "https://rebels.cs.uwaterloo.ca/papers/msr2014_mcintosh.pdf"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/codebase#code-ci-gates",
   "markdown_url": "https://goodcodereview.com/codebase.md"
  },
  {
   "id": "code-consistency-drift",
   "level": "codebase",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "correctness",
    "maintainability"
   ],
   "topics": [
    "consistency",
    "architecture"
   ],
   "lang": "core",
   "applies_when": "Auditing whether one concern is solved in several incompatible ways across the repo.",
   "context": [],
   "tool_gate": "Lint rules that ban the deprecated pattern once a standard is chosen.",
   "note": "Spotify, Uber, Cabify and Stripe back one standard per concern; tracking old variants has 2 (Stripe, Spotify), so it is in Why",
   "lacks": null,
   "rule": "For each recurring concern (errors, config, HTTP clients, logging), make one pattern the standard instead of leaving several as equal peers.",
   "why": "Each extra variant is another thing to learn, test, and secure, and drift accumulates through changes that each looked locally fine. Two limiters built two ways enforce the same quota differently. List the older variants and count them, so the migration shows progress and a new one shows up in review.",
   "counterpoint": "\"Forcing one way can block a better tool.\" Allow change through a proposal that updates the standard and the migration list, not through a quiet second pattern. Consistency breaks ties. It does not outrank a measured reason to switch.",
   "fail": {
    "language": "text",
    "code": "Three HTTP clients (requests, urllib3, httpx) with three timeout conventions;\nno document says which is current."
   },
   "pass": {
    "language": "text",
    "code": "docs: \"use httpx via net/client.py\". Lint bans direct requests imports;\nlegacy sites listed in MIGRATION.md."
   },
   "sources": [
    "https://blog.pragmaticengineer.com/good-code-reviews-better-code-reviews/",
    "https://google.github.io/eng-practices/review/reviewer/standard.html",
    "https://dev.to/sportebois/software-design-red-flags-wisdom-nuggets-from-john-ousterhout-43i2",
    "https://engineering.atspotify.com/2026/6/code-with-claude-coding-is-no-longer-the-constraint",
    "https://www.uber.com/blog/ubers-rate-limiting-system/",
    "https://tech.cabify.com/blog/engineering/cabify-container-certification",
    "https://stripe.dev/blog/sorbet-stripes-type-checker-for-ruby"
   ],
   "orgs": [
    {
     "id": "pragmatic-engineer",
     "name": "The Pragmatic Engineer",
     "sources": [
      "https://blog.pragmaticengineer.com/good-code-reviews-better-code-reviews/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://google.github.io/eng-practices/review/reviewer/standard.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "spotify",
     "name": "Spotify",
     "sources": [
      "https://engineering.atspotify.com/2026/6/code-with-claude-coding-is-no-longer-the-constraint"
     ],
     "strength": "unjudged"
    },
    {
     "id": "uber",
     "name": "Uber",
     "sources": [
      "https://www.uber.com/blog/ubers-rate-limiting-system/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cabify",
     "name": "Cabify",
     "sources": [
      "https://tech.cabify.com/blog/engineering/cabify-container-certification"
     ],
     "strength": "unjudged"
    },
    {
     "id": "stripe",
     "name": "Stripe",
     "sources": [
      "https://stripe.dev/blog/sorbet-stripes-type-checker-for-ruby"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "portebois",
     "name": "Sébastien Portebois",
     "sources": [
      "https://dev.to/sportebois/software-design-red-flags-wisdom-nuggets-from-john-ousterhout-43i2"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/codebase#code-consistency-drift",
   "markdown_url": "https://goodcodereview.com/codebase.md"
  },
  {
   "id": "code-dead-code",
   "level": "codebase",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "cleanup",
    "deletion"
   ],
   "lang": "core",
   "applies_when": "Auditing for code, flags and files that nothing uses.",
   "context": [],
   "tool_gate": "An unused-code linter in CI (vulture, knip, or deadcode); allowlist each intentional exception with a reason.",
   "note": "Commented-out code (Lockheed Martin JSF, JetBrains) and orphaned files (no source) have fewer than three organisations, so they sit in the Why. Palantir's page loads and backs only the commented-out-code case in the Why. Version-control history as the reason to delete has two organisations (Google, JetBrains), so it stays in the Why. JetBrains and Statsig are vendors.",
   "lacks": null,
   "rule": "Delete unreferenced functions and variables, and the branches of expired feature flags.",
   "why": "Dead code costs reading time, misleads searches, and gets \"fixed\" and tested for no benefit. The same holds for the common cases of commented-out code and files nothing links to: delete them too and let version control remember.",
   "counterpoint": "Code may be called by reflection, plugins, or external users. Check with the detector's config and a grep of consumers. For public API, deprecate first (see `chg-expand-contract`).",
   "fail": {
    "language": "text",
    "code": "def old_export(): ...      # no caller anywhere in the repo\nif FLAGS[\"legacy_ui\"]:     # flag permanently false since release 4\n    render_legacy()"
   },
   "pass": {
    "language": "text",
    "code": "Unused function and expired-flag branch deleted; version history shows where they went.\nThe unused-code linter reports 0 symbols, or each is allowlisted with a reason."
   },
   "sources": [
    "https://henrikwarne.com/2024/01/10/tidy-first/",
    "https://programmingisterrible.com/post/139222674273/write-code-that-is-easy-to-delete-not-easy-to",
    "https://blog.palantir.com/code-review-best-practices-19e02780015f",
    "https://abseil.io/resources/swe-book/html/ch09.html",
    "https://abseil.io/resources/swe-book/html/ch15.html",
    "https://engineering.fb.com/2023/10/24/data-infrastructure/automating-dead-code-cleanup/",
    "https://cmu-sei.github.io/secure-coding-standards/sei-cert-c-coding-standard/recommendations/miscellaneous-msc/msc12-c",
    "https://docs.kernel.org/process/coding-style.html",
    "https://github.com/NVIDIA/Model-Optimizer/blob/main/CONTRIBUTING.md",
    "https://learn.microsoft.com/en-us/devops/operate/progressive-experimentation-feature-flags",
    "https://docs.gitlab.com/ee/development/feature_flags/",
    "https://www.uber.com/blog/piranha/",
    "https://statsig.com/blog/feature-flag-code-cleanup",
    "https://www.jetbrains.com/help/inspectopedia/CommentedOutCode.html",
    "https://www.stroustrup.com/JSF-AV-rules.pdf"
   ],
   "orgs": [
    {
     "id": "palantir",
     "name": "Palantir",
     "sources": [
      "https://blog.palantir.com/code-review-best-practices-19e02780015f"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://abseil.io/resources/swe-book/html/ch09.html",
      "https://abseil.io/resources/swe-book/html/ch15.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "meta",
     "name": "Meta",
     "sources": [
      "https://engineering.fb.com/2023/10/24/data-infrastructure/automating-dead-code-cleanup/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cmu-sei",
     "name": "Carnegie Mellon SEI",
     "sources": [
      "https://cmu-sei.github.io/secure-coding-standards/sei-cert-c-coding-standard/recommendations/miscellaneous-msc/msc12-c"
     ],
     "strength": "unjudged"
    },
    {
     "id": "kernel",
     "name": "Linux kernel",
     "sources": [
      "https://docs.kernel.org/process/coding-style.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nvidia",
     "name": "NVIDIA",
     "sources": [
      "https://github.com/NVIDIA/Model-Optimizer/blob/main/CONTRIBUTING.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/devops/operate/progressive-experimentation-feature-flags"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://docs.gitlab.com/ee/development/feature_flags/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "uber",
     "name": "Uber",
     "sources": [
      "https://www.uber.com/blog/piranha/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "statsig",
     "name": "Statsig",
     "sources": [
      "https://statsig.com/blog/feature-flag-code-cleanup"
     ],
     "strength": "unjudged"
    },
    {
     "id": "jetbrains",
     "name": "JetBrains",
     "sources": [
      "https://www.jetbrains.com/help/inspectopedia/CommentedOutCode.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "lockheed",
     "name": "Lockheed Martin",
     "sources": [
      "https://www.stroustrup.com/JSF-AV-rules.pdf"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "warne",
     "name": "Henrik Warne",
     "sources": [
      "https://henrikwarne.com/2024/01/10/tidy-first/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "tef",
     "name": "tef (programmingisterrible)",
     "sources": [
      "https://programmingisterrible.com/post/139222674273/write-code-that-is-easy-to-delete-not-easy-to"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/codebase#code-dead-code",
   "markdown_url": "https://goodcodereview.com/codebase.md"
  },
  {
   "id": "code-dependency-licence-sbom",
   "level": "codebase",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "security"
   ],
   "topics": [
    "supply-chain",
    "licences",
    "sbom"
   ],
   "lang": "core",
   "applies_when": "Auditing what third-party code the repo ships and under which terms.",
   "context": [],
   "tool_gate": "A vulnerability scanner such as OSV-Scanner or Dependabot alerts; an SBOM generator such as Syft.",
   "note": "SBOM backed by OWASP, NIST and GitHub; scan by OWASP, OpenSSF, GitHub and Cabify (Trivy at build, running images rescanned every 6 hours); the licence allowlist is team policy, unsourced",
   "lacks": null,
   "rule": "Generate an SBOM, and scan dependencies for known vulnerabilities on every change or on a schedule. Each ask is judged separately: name which one failed on the FAIL line.",
   "why": "Without an inventory, a team cannot answer a disclosed vulnerability quickly. CI is the usual place for both steps: the SBOM then matches what was built, and the scan runs without anyone remembering it. Team policy: also fail the build on a dependency whose licence is not on the approved list.",
   "counterpoint": "SBOMs and scanners create noise that nobody triages. Tune severity thresholds and assign an owner, since an ignored report is worse than none. The licence list is team policy, not a universal rule. Drop it if your team has no licence constraints.",
   "fail": {
    "language": "text",
    "code": "No SBOM anywhere, no scanner in CI, and dependency alerts turned off."
   },
   "pass": {
    "language": "text",
    "code": "CI: generate SBOM, run vulnerability scanner, fail on high severity;\nrelease artifacts attach the SBOM."
   },
   "sources": [
    "https://owasp.org/Top10/2025/A03_2025-Software_Supply_Chain_Failures/",
    "https://github.com/ossf/scorecard/blob/main/docs/checks.md",
    "https://www.cisa.gov/sbom",
    "https://www.nist.gov/itl/executive-order-14028-improving-nations-cybersecurity/software-security-supply-chains-software-1",
    "https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-the-dependency-graph",
    "https://tech.cabify.com/blog/engineering/cabify-container-certification"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://owasp.org/Top10/2025/A03_2025-Software_Supply_Chain_Failures/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "openssf",
     "name": "OpenSSF",
     "sources": [
      "https://github.com/ossf/scorecard/blob/main/docs/checks.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cisa",
     "name": "CISA",
     "sources": [
      "https://www.cisa.gov/sbom"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nist",
     "name": "NIST",
     "sources": [
      "https://www.nist.gov/itl/executive-order-14028-improving-nations-cybersecurity/software-security-supply-chains-software-1"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-the-dependency-graph"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cabify",
     "name": "Cabify",
     "sources": [
      "https://tech.cabify.com/blog/engineering/cabify-container-certification"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/codebase#code-dependency-licence-sbom",
   "markdown_url": "https://goodcodereview.com/codebase.md"
  },
  {
   "id": "code-dependency-pinning",
   "level": "codebase",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "supply-chain",
    "dependencies"
   ],
   "lang": "core",
   "applies_when": "Auditing how the repo resolves third-party packages.",
   "context": [],
   "tool_gate": "Lockfile-required install in CI; dependency-update bot config present.",
   "note": "Install must fail on a stale lock, not re-resolve (npm ci errors when the lock and package.json disagree; pip --require-hashes rejects unhashed requirements). The Go and uv/poetry equivalents are the same rule applied to their tools; the sources name npm and pip only",
   "lacks": null,
   "rule": "Commit a lockfile with hashes and install from it in CI.",
   "why": "Unpinned versions make builds non-reproducible and let a compromised release flow straight into production. Updates should arrive as reviewed, staged changes, not silently. [[code-workflow-supply-chain]] covers CI action pins.",
   "counterpoint": "Pinning delays security patches. A pin plus an update bot with a defined cadence beats floating versions, because the lock makes the update visible and revertable.",
   "fail": {
    "language": "text",
    "code": "requirements.txt:  requests>=2   (no lockfile; CI runs pip install -U)"
   },
   "pass": {
    "language": "text",
    "code": "requirements.lock with hashes; CI: pip install --require-hashes -r requirements.lock\nUpdate bot opens PRs; changes are rolled out in stages."
   },
   "sources": [
    "https://owasp.org/Top10/2025/A03_2025-Software_Supply_Chain_Failures/",
    "https://github.com/ossf/scorecard/blob/main/docs/checks.md",
    "https://sre.google/sre-book/release-engineering/",
    "https://pip.pypa.io/en/stable/topics/secure-installs/",
    "https://docs.npmjs.com/cli/v10/commands/npm-ci"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://owasp.org/Top10/2025/A03_2025-Software_Supply_Chain_Failures/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "openssf",
     "name": "OpenSSF",
     "sources": [
      "https://github.com/ossf/scorecard/blob/main/docs/checks.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://sre.google/sre-book/release-engineering/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://pip.pypa.io/en/stable/topics/secure-installs/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "npm",
     "name": "npm",
     "sources": [
      "https://docs.npmjs.com/cli/v10/commands/npm-ci"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/codebase#code-dependency-pinning",
   "markdown_url": "https://goodcodereview.com/codebase.md"
  },
  {
   "id": "code-docs-refs-resolve",
   "level": "codebase",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "docs",
    "ci"
   ],
   "lang": "core",
   "applies_when": "Auditing a repo whose README, guides or AGENTS.md cite file paths, anchors or other pages.",
   "context": [],
   "tool_gate": "A link checker in CI, such as lychee (`lychee --offline --include-fragments docs README.md`) or mdBook linkcheck.",
   "note": "GitHub docs, GitLab docs and the Rust dev guide each run a link check on every change. Whether the documented commands run is a different rule",
   "lacks": null,
   "rule": "Run a link check in CI over the docs, so that every relative link and anchor they cite resolves.",
   "why": "A rename or a deleted file leaves docs that point at nothing. Readers and agents then follow a dead path, and nobody notices until someone trips on it. A machine finds a dangling reference on the pull request that causes it.",
   "counterpoint": "\"External links flake, so the job will be red half the time.\" Check local paths and anchors on every change, where the result is stable. Run the external links on a schedule, so a dead site never blocks a merge.",
   "fail": {
    "language": "text",
    "code": "README.md:  See [the deploy guide](docs/deploy.md).      <- docs/deploy.md was renamed to docs/ops/deploy.md\nAGENTS.md:  See [Setup](docs/dev.md#setup).             <- the heading was renamed\nCI:         no link check, no job reads the docs"
   },
   "pass": {
    "language": "yaml",
    "code": "docs-links:\n  runs-on: ubuntu-latest\n  steps:\n    - uses: actions/checkout@<40-char-sha>\n    - run: lychee --offline --include-fragments README.md AGENTS.md docs"
   },
   "sources": [
    "https://github.com/github/docs/blob/main/src/links/README.md",
    "https://docs.gitlab.com/development/documentation/testing/links/",
    "https://github.com/rust-lang/rustc-dev-guide/blob/master/.github/workflows/ci.yml"
   ],
   "orgs": [
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://github.com/github/docs/blob/main/src/links/README.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://docs.gitlab.com/development/documentation/testing/links/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "rust",
     "name": "Rust project",
     "sources": [
      "https://github.com/rust-lang/rustc-dev-guide/blob/master/.github/workflows/ci.yml"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/codebase#code-docs-refs-resolve",
   "markdown_url": "https://goodcodereview.com/codebase.md"
  },
  {
   "id": "code-file-lines-ceiling",
   "level": "codebase",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "maintainability",
    "process"
   ],
   "topics": [
    "structure",
    "size"
   ],
   "lang": "core",
   "applies_when": "Auditing a repo for files that have grown past comprehension.",
   "context": [],
   "tool_gate": "Pylint too-many-lines (C0302, max-module-lines default 1000), ESLint max-lines, or Checkstyle FileLength, with an allowlist for generated files.",
   "note": "a documented file-length limit - Pylint (default 1000), ESLint (default 300; recommendations \"usually range from 100 to 500\"), Checkstyle (default 2000), RuboCop (class and module length, default 100) and PMD (size and complexity caps); the numbers differ, so the Rule asks for a limit and not for 1,000",
   "lacks": null,
   "rule": "Set a file-length limit in the linter (Pylint and SonarSource default to 1,000 lines), keep hand-written files under it unless a listed exception says otherwise, and split larger files by knowledge owned, not by execution order.",
   "why": "Huge files hide unrelated decisions, attract merge conflicts, and cannot be understood in one read. Linters ship a default for this because a file that passes it is \"hard to understand\" (Checkstyle). The defaults differ from 300 to 2,000 lines, so pick one and enforce it.",
   "counterpoint": "Ousterhout favours deep modules and a long file can be coherent. Splitting for its own sake adds shallow pieces. 1,000 lines is a trigger for a look, not a verdict. The fix is to separate decisions (his information-leakage and temporal-decomposition flags).",
   "fail": {
    "language": "text",
    "code": "$ wc -l src/*.py | sort -n | tail -2\n  4,812 src/utils.py\n  3,105 src/app.py"
   },
   "pass": {
    "language": "text",
    "code": "pylint: max-module-lines = 1000; no hand-written file over it; generated files listed in an allowlist."
   },
   "sources": [
    "https://pylint.readthedocs.io/en/stable/user_guide/configuration/all-options.html",
    "https://eslint.org/docs/latest/rules/max-lines",
    "https://checkstyle.sourceforge.io/checks/sizes/filelength.html",
    "https://docs.rubocop.org/rubocop/latest/cops_metrics.html",
    "https://docs.pmd-code.org/latest/pmd_rules_java_design.html",
    "https://dev.to/sportebois/software-design-red-flags-wisdom-nuggets-from-john-ousterhout-43i2",
    "https://programmingisterrible.com/post/139222674273/write-code-that-is-easy-to-delete-not-easy-to"
   ],
   "orgs": [
    {
     "id": "pylint",
     "name": "Pylint",
     "sources": [
      "https://pylint.readthedocs.io/en/stable/user_guide/configuration/all-options.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "eslint",
     "name": "ESLint",
     "sources": [
      "https://eslint.org/docs/latest/rules/max-lines"
     ],
     "strength": "weak"
    },
    {
     "id": "checkstyle",
     "name": "Checkstyle",
     "sources": [
      "https://checkstyle.sourceforge.io/checks/sizes/filelength.html"
     ],
     "strength": "weak"
    },
    {
     "id": "rubocop",
     "name": "RuboCop",
     "sources": [
      "https://docs.rubocop.org/rubocop/latest/cops_metrics.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "pmd",
     "name": "PMD",
     "sources": [
      "https://docs.pmd-code.org/latest/pmd_rules_java_design.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "portebois",
     "name": "Sébastien Portebois",
     "sources": [
      "https://dev.to/sportebois/software-design-red-flags-wisdom-nuggets-from-john-ousterhout-43i2"
     ],
     "strength": "unjudged"
    },
    {
     "id": "tef",
     "name": "tef (programmingisterrible)",
     "sources": [
      "https://programmingisterrible.com/post/139222674273/write-code-that-is-easy-to-delete-not-easy-to"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "mixed",
   "url": "https://goodcodereview.com/codebase#code-file-lines-ceiling",
   "markdown_url": "https://goodcodereview.com/codebase.md"
  },
  {
   "id": "code-flaky-test-quarantine",
   "level": "codebase",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "testing",
    "process"
   ],
   "topics": [
    "ci",
    "flaky-tests"
   ],
   "lang": "core",
   "applies_when": "Auditing how the codebase handles tests that pass and fail on the same code.",
   "context": [],
   "tool_gate": null,
   "note": "report and route flaky tests - Dropbox (Quarantine Keeper removes tests that fail too often and files a bug against the owner), Shopify (retries a failed test up to three times and notifies developers of every pass after a retry); rerun-verified fix - Dropbox only (100 or more CI runs); causes - Shopify (fixed pauses instead of waits), Cabify (state kept between tests), Dropbox (short timeouts, leftover state); only 2 orgs back the Rule",
   "lacks": null,
   "rule": "Report every test that passes only on retry, move it to a quarantine with a named owner and a ticket, and return it to the merge gate only after it passes many reruns.",
   "why": "A test that fails at random on unchanged code teaches people to rerun until green, and then a real regression looks like noise. Dropbox removes tests that fail too often from the main pool and files a bug against the owner. Its fix workflow then runs the test 100 or more times in CI before the fix lands. Shopify retries a failed test but tells developers about every pass after a retry. The usual causes are a fixed pause where the test should wait for an element (Shopify) and state left behind by an earlier test (Cabify, Dropbox).",
   "counterpoint": "\"Retries keep the build green, and quarantine hides coverage.\" Shopify's data backs retries: a slightly slower build beats an engineer rerunning it by hand. Retries are fine when each pass after a retry reaches someone. A quarantined test still runs outside the gate, and its owner and ticket stop it from staying there for good. Skipping or deleting a flaky test in a diff is a separate FAIL. See [[chg-no-silenced-diagnostics]]. For the merge gate itself, see [[code-ci-gates]].",
   "fail": {
    "language": "yaml",
    "code": "# .github/workflows/test.yml\n- run: pytest --reruns 3          # a pass on retry counts as a pass, and nobody hears about it"
   },
   "pass": {
    "language": "yaml",
    "code": "# .github/workflows/test.yml\n- run: pytest --reruns 3 -m \"not quarantine\" --junitxml=report.xml\n- run: python tools/flaky_report.py report.xml   # opens a ticket per pass after retry, owner from CODEOWNERS\n# nightly.yml: a quarantined test rejoins the gate after 100 clean runs\n- run: pytest -m quarantine --count 100"
   },
   "sources": [
    "https://dropbox.tech/application/accelerating-iteration-velocity-on-dropboxs-desktop-client-part-1",
    "https://dropbox.tech/machine-learning/introducing-nova-our-internal-platform-for-coding-agents",
    "https://shopify.engineering/unreasonable-effectiveness-test-retries-android-monorepo-case-study",
    "https://shopify.engineering/mobile-e2e-testing",
    "https://tech.cabify.com/blog/engineering/acceptance-testing-go-services-using-aceptadora",
    "https://www.atlassian.com/blog/atlassian-engineering/taming-test-flakiness-how-we-built-a-scalable-tool-to-detect-and-manage-flaky-tests",
    "https://handbook.gitlab.com/handbook/engineering/testing/quarantine-process/",
    "https://medium.com/airtable-eng/how-airtable-manages-flaky-tests-in-a-large-scale-monorepo-5fe09922e90c"
   ],
   "orgs": [
    {
     "id": "dropbox",
     "name": "Dropbox",
     "sources": [
      "https://dropbox.tech/application/accelerating-iteration-velocity-on-dropboxs-desktop-client-part-1",
      "https://dropbox.tech/machine-learning/introducing-nova-our-internal-platform-for-coding-agents"
     ],
     "strength": "unjudged"
    },
    {
     "id": "shopify",
     "name": "Shopify",
     "sources": [
      "https://shopify.engineering/unreasonable-effectiveness-test-retries-android-monorepo-case-study",
      "https://shopify.engineering/mobile-e2e-testing"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cabify",
     "name": "Cabify",
     "sources": [
      "https://tech.cabify.com/blog/engineering/acceptance-testing-go-services-using-aceptadora"
     ],
     "strength": "unjudged"
    },
    {
     "id": "atlassian",
     "name": "Atlassian",
     "sources": [
      "https://www.atlassian.com/blog/atlassian-engineering/taming-test-flakiness-how-we-built-a-scalable-tool-to-detect-and-manage-flaky-tests"
     ],
     "strength": "supports"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://handbook.gitlab.com/handbook/engineering/testing/quarantine-process/"
     ],
     "strength": "supports"
    },
    {
     "id": "airtable",
     "name": "Airtable",
     "sources": [
      "https://medium.com/airtable-eng/how-airtable-manages-flaky-tests-in-a-large-scale-monorepo-5fe09922e90c"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "strong",
   "url": "https://goodcodereview.com/codebase#code-flaky-test-quarantine",
   "markdown_url": "https://goodcodereview.com/codebase.md"
  },
  {
   "id": "code-perf-budget-in-ci",
   "level": "codebase",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "performance",
    "process"
   ],
   "topics": [
    "ci"
   ],
   "lang": "core",
   "applies_when": "The repo has a performance target (bundle size, page metrics, latency under load, or a benchmark) that matters to users.",
   "context": [],
   "tool_gate": "Lighthouse CI assert, size-limit, k6 thresholds, Gatling assertions, or a benchstat comparison in CI.",
   "note": null,
   "lacks": null,
   "rule": "Write the performance budget down as a CI check that fails the build when it is exceeded.",
   "why": "Performance regresses a few percent at a time, and no reviewer sees a 3% slowdown in a diff. A budget that only warns becomes a log line nobody reads.",
   "counterpoint": "\"Benchmarks in CI are noisy and flake.\" They are when a gate trusts one run. Gate on a stable budget, compare repeated runs (benchstat wants 10 to 20), and keep the threshold above the noise. On a site with much room to improve, a tight budget only turns every build red, so set it at today's level and lower it later.",
   "fail": {
    "language": "text",
    "code": "lighthouserc.json: \"assertions\": { \"categories:performance\": [\"warn\", { \"minScore\": 0.9 }] }\nk6 script with no thresholds block"
   },
   "pass": {
    "language": "text",
    "code": "size-limit: [{ \"path\": \"dist/app.js\", \"limit\": \"180 kB\" }]   # npm run size in CI\nk6: thresholds: { http_req_duration: [\"p(95)<300\"] }        # non-zero exit fails the job"
   },
   "sources": [
    "https://github.com/GoogleChrome/lighthouse-ci/blob/main/docs/getting-started.md",
    "https://github.com/ai/size-limit",
    "https://grafana.com/docs/k6/latest/using-k6/thresholds/",
    "https://docs.gatling.io/concepts/assertions/",
    "https://learn.microsoft.com/en-us/azure/well-architected/performance-efficiency/performance-test",
    "https://docs.aws.amazon.com/wellarchitected/latest/performance-efficiency-pillar/perf_process_culture_load_test.html",
    "https://firefox-source-docs.mozilla.org/testing/perfdocs/perf-sheriffing.html",
    "https://pkg.go.dev/golang.org/x/perf/cmd/benchstat"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://github.com/GoogleChrome/lighthouse-ci/blob/main/docs/getting-started.md",
      "https://pkg.go.dev/golang.org/x/perf/cmd/benchstat"
     ],
     "strength": "unjudged"
    },
    {
     "id": "sitnik",
     "name": "Size Limit project",
     "sources": [
      "https://github.com/ai/size-limit"
     ],
     "strength": "unjudged"
    },
    {
     "id": "grafana",
     "name": "Grafana Labs",
     "sources": [
      "https://grafana.com/docs/k6/latest/using-k6/thresholds/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gatling",
     "name": "Gatling",
     "sources": [
      "https://docs.gatling.io/concepts/assertions/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/well-architected/performance-efficiency/performance-test"
     ],
     "strength": "unjudged"
    },
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://docs.aws.amazon.com/wellarchitected/latest/performance-efficiency-pillar/perf_process_culture_load_test.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://firefox-source-docs.mozilla.org/testing/perfdocs/perf-sheriffing.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/codebase#code-perf-budget-in-ci",
   "markdown_url": "https://goodcodereview.com/codebase.md"
  },
  {
   "id": "code-release-gated-on-ci",
   "level": "codebase",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "reliability",
    "process"
   ],
   "topics": [
    "ci",
    "release"
   ],
   "lang": "core",
   "applies_when": "Auditing a workflow or pipeline that publishes a release when a tag is pushed.",
   "context": [],
   "tool_gate": "Release job with `needs:` the test job, or a `workflow_run` trigger checked for `conclusion == 'success'`; a tag ruleset with required status checks.",
   "note": "code-ci-gates covers merge to the default branch, and this card covers the tag-to-publish path, which a merge gate does not cover",
   "lacks": null,
   "rule": "Make a release or publish job depend on the checks passing for the tagged commit, never on the tag alone.",
   "why": "A tag can point at a commit that was never built or tested, or one whose checks are red. A workflow that fires on the tag then ships it. Branch protection does not help, because the tag does not go through a pull request.",
   "counterpoint": "\"CI already passed on the pull request, so rerunning tests on the tag wastes time.\" The pull request tested another commit if the base moved or the tag was cut elsewhere. Reuse the result by requiring the status check on the tagged commit, instead of dropping the gate.",
   "fail": {
    "language": "yaml",
    "code": "on:\n  push:\n    tags: [\"v*\"]\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@<40-char-sha>\n      - run: make publish"
   },
   "pass": {
    "language": "yaml",
    "code": "on:\n  push:\n    tags: [\"v*\"]\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@<40-char-sha>\n      - run: make test\n  publish:\n    needs: test\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@<40-char-sha>\n      - run: make publish"
   },
   "sources": [
    "https://github.com/kubernetes/sig-release/blob/master/release-blocking-jobs.md",
    "https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-jobs",
    "https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets",
    "https://docs.gitlab.com/ci/pipelines/",
    "https://github.com/nodejs/node/blob/main/doc/contributing/releases.md",
    "https://github.com/envoyproxy/envoy/blob/main/RELEASES.md",
    "https://sre.google/sre-book/release-engineering/"
   ],
   "orgs": [
    {
     "id": "kubernetes",
     "name": "Kubernetes",
     "sources": [
      "https://github.com/kubernetes/sig-release/blob/master/release-blocking-jobs.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-jobs",
      "https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://docs.gitlab.com/ci/pipelines/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nodejs",
     "name": "OpenJS Foundation",
     "sources": [
      "https://github.com/nodejs/node/blob/main/doc/contributing/releases.md"
     ],
     "strength": "supports"
    },
    {
     "id": "envoy",
     "name": "Envoy",
     "sources": [
      "https://github.com/envoyproxy/envoy/blob/main/RELEASES.md"
     ],
     "strength": "supports"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://sre.google/sre-book/release-engineering/"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "strong",
   "url": "https://goodcodereview.com/codebase#code-release-gated-on-ci",
   "markdown_url": "https://goodcodereview.com/codebase.md"
  },
  {
   "id": "code-rule-has-enforcer",
   "level": "codebase",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "tooling",
    "conventions"
   ],
   "lang": "core",
   "applies_when": "Auditing any written repo rule (AGENTS.md, CONTRIBUTING, a docs page, a code comment, repeated review comments) that a type check, lint rule, test or CI check could enforce.",
   "context": [],
   "tool_gate": "The enforcer itself: a type, a lint rule or a CI check.",
   "note": "Spotify, Stripe and Cabify back the Rule without cursor/plugins",
   "lacks": null,
   "rule": "Back each written repo rule that a type, lint or CI check could enforce with that check, not with a line in an instruction file or a repeated review comment.",
   "why": "A rule that relies on memory gets broken, and reviewers then repeat the same correction. An instruction file is advisory: a person or an agent can skip it. A lint message that names the fix teaches it at the moment of the mistake. A \"do not remove\" code comment has the same weakness as an instruction file line.",
   "counterpoint": "\"Not every rule is checkable.\" Right, and judgement rules stay as prose. The FAIL is a checkable rule with no check. See [[code-ci-gates]] for where checks run, and [[chg-review-signal]] for what reviewers stop commenting on once a tool owns it.",
   "fail": {
    "language": "text",
    "code": "CONTRIBUTING.md: \"never call requests.get without a timeout\"   (no lint rule)\nReview history: the same comment on 6 PRs"
   },
   "pass": {
    "language": "toml",
    "code": "# pyproject.toml, checked by ruff in CI\n[tool.ruff.lint]\nextend-select = [\"S113\"]   # request-without-timeout"
   },
   "sources": [
    "https://github.com/cursor/plugins/blob/main/pstack/skills/principle-encode-lessons-in-structure/SKILL.md",
    "https://github.com/cursor/plugins/blob/main/pstack/skills/correct/SKILL.md",
    "https://github.com/cursor/plugins/blob/main/pstack/skills/no-comments/SKILL.md",
    "https://github.com/cursor/plugins/blob/main/pstack/skills/interrogate/references/rubric.md",
    "https://engineering.atspotify.com/2026/9/portal-by-spotify-cut-my-claude-code-token-usage-by-90",
    "https://engineering.atspotify.com/2026/6/code-with-claude-coding-is-no-longer-the-constraint",
    "https://stripe.dev/blog/formatting-an-entire-25-million-line-codebase-overnight-the-rubyfmt-story",
    "https://stripe.dev/blog/sorbet-stripes-type-checker-for-ruby",
    "https://tech.cabify.com/blog/engineering/cabify-container-certification"
   ],
   "orgs": [
    {
     "id": "cursor",
     "name": "Cursor",
     "sources": [
      "https://github.com/cursor/plugins/blob/main/pstack/skills/principle-encode-lessons-in-structure/SKILL.md",
      "https://github.com/cursor/plugins/blob/main/pstack/skills/correct/SKILL.md",
      "https://github.com/cursor/plugins/blob/main/pstack/skills/no-comments/SKILL.md",
      "https://github.com/cursor/plugins/blob/main/pstack/skills/interrogate/references/rubric.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "spotify",
     "name": "Spotify",
     "sources": [
      "https://engineering.atspotify.com/2026/9/portal-by-spotify-cut-my-claude-code-token-usage-by-90",
      "https://engineering.atspotify.com/2026/6/code-with-claude-coding-is-no-longer-the-constraint"
     ],
     "strength": "unjudged"
    },
    {
     "id": "stripe",
     "name": "Stripe",
     "sources": [
      "https://stripe.dev/blog/formatting-an-entire-25-million-line-codebase-overnight-the-rubyfmt-story",
      "https://stripe.dev/blog/sorbet-stripes-type-checker-for-ruby"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cabify",
     "name": "Cabify",
     "sources": [
      "https://tech.cabify.com/blog/engineering/cabify-container-certification"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/codebase#code-rule-has-enforcer",
   "markdown_url": "https://goodcodereview.com/codebase.md"
  },
  {
   "id": "code-scoped-validation-loop",
   "level": "codebase",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "testing",
    "process"
   ],
   "topics": [
    "onboarding"
   ],
   "lang": "core",
   "applies_when": "Auditing how a contributor checks a small change.",
   "context": [],
   "tool_gate": "A documented command that runs tests and lint for one path or package.",
   "note": "one vendor's engineering guidance (opinion), from agent-compatibility validation-review",
   "lacks": null,
   "rule": "Document a command that checks one package or path, with readable output, so a small change does not need the full-repo run.",
   "why": "If the only check takes twenty minutes or prints thousands of lines, people skip it and push to CI, and agents cannot tell which failure is theirs.",
   "counterpoint": "\"CI runs everything anyway.\" CI is the last check, not the loop. A scoped command lets the author find a failure in seconds instead of after a push.",
   "fail": {
    "language": "text",
    "code": "CONTRIBUTING.md: \"run `make test` (all 14,000 tests, ~25 min)\""
   },
   "pass": {
    "language": "text",
    "code": "pytest tests/billing -q        # about 20 s\nruff check src/billing"
   },
   "sources": [
    "https://github.com/cursor/plugins/blob/main/agent-compatibility/agents/validation-review.md",
    "https://android.googlesource.com/platform/frameworks/support/+/17957fcb324a42599abc868ffe8204a0384b2aee/camera/AGENTS.md",
    "https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/unit-tests/",
    "https://devguide.python.org/testing/run-write-tests/"
   ],
   "orgs": [
    {
     "id": "cursor",
     "name": "Cursor",
     "sources": [
      "https://github.com/cursor/plugins/blob/main/agent-compatibility/agents/validation-review.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://android.googlesource.com/platform/frameworks/support/+/17957fcb324a42599abc868ffe8204a0384b2aee/camera/AGENTS.md"
     ],
     "strength": "supports"
    },
    {
     "id": "django",
     "name": "Django Software Foundation",
     "sources": [
      "https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/unit-tests/"
     ],
     "strength": "supports"
    },
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://devguide.python.org/testing/run-write-tests/"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "strong",
   "url": "https://goodcodereview.com/codebase#code-scoped-validation-loop",
   "markdown_url": "https://goodcodereview.com/codebase.md"
  },
  {
   "id": "code-secret-scan",
   "level": "codebase",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security",
    "process"
   ],
   "topics": [
    "secrets",
    "ci"
   ],
   "lang": "core",
   "applies_when": "Any repository, including its history, issues and pull requests.",
   "context": [],
   "tool_gate": "Secret scanning on push (provider or pre-commit hook) with push protection on.",
   "note": null,
   "lacks": null,
   "rule": "Scan for committed credentials, and treat any hit as leaked: revoke and replace it first. With no scanner present and no secret found, the FAIL is the missing control; evidence is \"absent in <paths>\". A platform scanner (GitHub secret scanning with push protection, GitLab secret detection) counts when it is enabled on the repo and covers history.",
   "why": "A secret in history is readable by everyone with clone access, and forks and caches keep it after a rewrite. Rewriting history is slow and often unnecessary once the secret is dead.",
   "counterpoint": "\"Scanners give false positives.\" Tune with an allowlist of reviewed, non-secret strings. A missed live key costs far more than a triaged alert. [[sys-secrets-and-errors]] covers where secrets live at runtime.",
   "fail": {
    "language": "text",
    "code": "git log -p | rg 'AKIA'      # a live cloud key in an old commit; \"removed it in the next commit\""
   },
   "pass": {
    "language": "text",
    "code": "scanner runs on every push and covers history\nkey revoked and reissued the same day; new value comes from the secret store"
   },
   "sources": [
    "https://docs.github.com/en/code-security/secret-scanning/introduction/about-secret-scanning",
    "https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html",
    "https://www.cisa.gov/resources-tools/resources/product-security-bad-practices",
    "https://docs.gitlab.com/user/application_security/secret_detection/"
   ],
   "orgs": [
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://docs.github.com/en/code-security/secret-scanning/introduction/about-secret-scanning"
     ],
     "strength": "unjudged"
    },
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cisa",
     "name": "CISA",
     "sources": [
      "https://www.cisa.gov/resources-tools/resources/product-security-bad-practices"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://docs.gitlab.com/user/application_security/secret_detection/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/codebase#code-secret-scan",
   "markdown_url": "https://goodcodereview.com/codebase.md"
  },
  {
   "id": "code-test-fixtures-built-once",
   "level": "codebase",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "testing",
    "performance"
   ],
   "topics": [
    "ci",
    "fixtures"
   ],
   "lang": "core",
   "applies_when": "Auditing a test suite in which many tests share an expensive setup (a database schema and seed data, a container, a loaded application context).",
   "context": [],
   "tool_gate": "A CI job timeout plus a slowest-tests report in the build log (pytest or Django --durations, Gradle build scan).",
   "note": "build once - Django (setUpTestData), Spring (context cache), Laravel (RefreshDatabase skips the migration), Microsoft (create and seed \"exactly once\"), Alloy.ai (migrate once per suite), GitLab (let_it_be); reset by rollback - Django, Rails, Spring, Laravel, Ecto, SQLAlchemy, Microsoft, Alloy.ai, GitLab; truncate for tests that must commit - Django, Microsoft",
   "lacks": null,
   "rule": "Build an expensive fixture once per test run, and reset state between tests with a rollback or a truncate, never a rebuild.",
   "why": "A fixture that takes 20 seconds costs 20 seconds times the number of tests that rebuild it, and adding tests adds that cost again. One team measured about 10 seconds to drop, recreate and migrate a database per test and a 14-minute suite for 78 integration tests; wrapping each test in a rolled-back transaction cut it to 8 minutes. A slow suite then gets skipped locally, and changes arrive in larger, riskier batches.",
   "counterpoint": "\"A shared fixture breaks test isolation and blocks parallel runs.\" It does when the shared part is mutable or when tests commit. Keep the shared part read-only, undo each test's writes by rollback, give each parallel worker its own database, and give the few tests that must commit a separate database. A database kept between runs goes stale after a schema change, so rebuild it when the migrations change.",
   "fail": {
    "language": "text",
    "code": "before each test:\n    create database; run all migrations; load seed data     # 20-30 s\nafter each test:\n    drop database"
   },
   "pass": {
    "language": "text",
    "code": "once per run (session, test binary, or global setup):\n    create database; run all migrations; load seed data\nbefore each test:  begin a transaction (or savepoint)\nafter each test:   roll it back\ntests that must commit: their own database, cleaned with DELETE or TRUNCATE after each test, run serially"
   },
   "sources": [
    "https://docs.djangoproject.com/en/5.1/topics/testing/tools/",
    "https://guides.rubyonrails.org/testing.html",
    "https://docs.spring.io/spring-framework/reference/testing/testcontext-framework/ctx-management/caching.html",
    "https://docs.spring.io/spring-framework/reference/testing/testcontext-framework/tx.html",
    "https://laravel.com/docs/12.x/database-testing",
    "https://hexdocs.pm/ecto_sql/Ecto.Adapters.SQL.Sandbox.html",
    "https://docs.sqlalchemy.org/en/20/orm/session_transaction.html#joining-a-session-into-an-external-transaction-such-as-for-test-suites",
    "https://learn.microsoft.com/en-us/ef/core/testing/testing-with-the-database",
    "https://alloy.ai/speeding-up-integration-tests-db",
    "https://docs.gitlab.com/development/testing_guide/best_practices/"
   ],
   "orgs": [
    {
     "id": "django",
     "name": "Django Software Foundation",
     "sources": [
      "https://docs.djangoproject.com/en/5.1/topics/testing/tools/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "rails",
     "name": "Ruby on Rails",
     "sources": [
      "https://guides.rubyonrails.org/testing.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "spring",
     "name": "Spring",
     "sources": [
      "https://docs.spring.io/spring-framework/reference/testing/testcontext-framework/ctx-management/caching.html",
      "https://docs.spring.io/spring-framework/reference/testing/testcontext-framework/tx.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "laravel",
     "name": "Laravel",
     "sources": [
      "https://laravel.com/docs/12.x/database-testing"
     ],
     "strength": "unjudged"
    },
    {
     "id": "ecto",
     "name": "Ecto",
     "sources": [
      "https://hexdocs.pm/ecto_sql/Ecto.Adapters.SQL.Sandbox.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "sqlalchemy",
     "name": "SQLAlchemy",
     "sources": [
      "https://docs.sqlalchemy.org/en/20/orm/session_transaction.html#joining-a-session-into-an-external-transaction-such-as-for-test-suites"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/ef/core/testing/testing-with-the-database"
     ],
     "strength": "unjudged"
    },
    {
     "id": "alloy",
     "name": "Alloy.ai",
     "sources": [
      "https://alloy.ai/speeding-up-integration-tests-db"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://docs.gitlab.com/development/testing_guide/best_practices/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/codebase#code-test-fixtures-built-once",
   "markdown_url": "https://goodcodereview.com/codebase.md"
  },
  {
   "id": "code-test-selection-on-pr",
   "level": "codebase",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "testing",
    "performance",
    "process"
   ],
   "topics": [
    "ci"
   ],
   "lang": "core",
   "applies_when": "A pull-request CI job runs a test suite that is slower than the team's feedback target for a pull request. Mark `N/A` when the full suite already fits the target.",
   "context": [],
   "tool_gate": "A PR job that runs the affected-tests command (`nx affected -t test`, Azure Test Impact Analysis, Develocity Predictive Test Selection), plus a full-suite job off the PR path.",
   "note": "Meta, Microsoft, Shopify, Nx and Gradle (Develocity) select tests on the PR; Microsoft, Shopify and Gradle keep a full run before deploy or on a schedule (Meta's post does not say). Google's presubmit runs a team-chosen fast subset, not change-based selection",
   "lacks": null,
   "rule": "When the full suite is slower than the pull-request feedback target, run only the tests the change affects on the pull request, and run the full suite before deploy or on a schedule.",
   "why": "A full suite on every pull request makes the queue wait for tests the change cannot break. Meta runs a third of the affected tests and reports catching more than 99.9 percent of regressions. Shopify runs the tests related to the change. The full run still happens, outside the pull-request gate.",
   "counterpoint": "\"Selection misses regressions.\" Shopify says a stale mapping let failing tests merge, about 0.06 percent of pull requests, and Microsoft falls back to all tests when it cannot classify a change. Keep the full run, fall back to all tests when the mapping cannot decide, and do not present the pull-request run as full coverage. The selected tests gate the merge, as [[code-ci-gates]] asks; the full run comes before deploy or on a schedule.",
   "fail": {
    "language": "yaml",
    "code": "# the only test job\non: pull_request\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - run: pytest          # 40-minute suite on every PR, no nightly or pre-deploy run"
   },
   "pass": {
    "language": "yaml",
    "code": "# .github/workflows/pr.yml\non: pull_request\njobs:\n  affected:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - run: nx affected -t test\n\n# .github/workflows/nightly.yml\non:\n  schedule: [{ cron: \"0 3 * * *\" }]\njobs:\n  full:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - run: pytest"
   },
   "sources": [
    "https://engineering.fb.com/2018/11/21/developer-tools/predictive-test-selection/",
    "https://learn.microsoft.com/en-us/azure/devops/pipelines/test/test-impact-analysis?view=azure-devops",
    "https://shopify.engineering/spark-joy-by-running-fewer-tests",
    "https://abseil.io/resources/swe-book/html/ch23.html",
    "https://nx.dev/ci/features/affected",
    "https://docs.develocity.ai/current/using-develocity/predictive-test-selection/"
   ],
   "orgs": [
    {
     "id": "meta",
     "name": "Meta",
     "sources": [
      "https://engineering.fb.com/2018/11/21/developer-tools/predictive-test-selection/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/devops/pipelines/test/test-impact-analysis?view=azure-devops"
     ],
     "strength": "unjudged"
    },
    {
     "id": "shopify",
     "name": "Shopify",
     "sources": [
      "https://shopify.engineering/spark-joy-by-running-fewer-tests"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://abseil.io/resources/swe-book/html/ch23.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nx",
     "name": "Nx",
     "sources": [
      "https://nx.dev/ci/features/affected"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gradle",
     "name": "Gradle",
     "sources": [
      "https://docs.develocity.ai/current/using-develocity/predictive-test-selection/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/codebase#code-test-selection-on-pr",
   "markdown_url": "https://goodcodereview.com/codebase.md"
  },
  {
   "id": "code-vuln-remediation-window",
   "level": "codebase",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "security",
    "process"
   ],
   "topics": [
    "supply-chain"
   ],
   "lang": "core",
   "applies_when": "The repo has open dependency alerts, update pull requests, or scan reports; with none open, mark it N/A.",
   "context": [],
   "tool_gate": "Dependabot or Renovate alerts with a severity SLA; OSV-Scanner or Scorecard `Vulnerabilities` in CI.",
   "note": "Windows from OpenSSF (60 days for medium and above), CISA (two weeks for exploited CVEs) and GitHub (an SLA per severity). Risk acceptance is OWASP and GitHub only, so it sits in the Why",
   "lacks": null,
   "rule": "Write down how long a known-vulnerable dependency may stay unfixed and fix or upgrade it inside that window.",
   "why": "A scan that finds a vulnerability and then sits unread protects nobody. An alert left open past its window is the failure, whatever the tool says. If a fix is not possible, record the accepted risk with an owner and a review date, so the open alert is a decision and not an oversight. [[code-dependency-licence-sbom]] covers running the scan, and this card covers acting on the result.",
   "counterpoint": "\"Most alerts are not reachable in our code, so the window is busywork.\" Then triage the alert inside the window and dismiss it with the reason. Reachability is a finding to record, not a reason to skip the clock.",
   "fail": {
    "language": "text",
    "code": "Dependabot: 14 open alerts, 3 critical, oldest opened 210 days ago.\nOpen update PRs for those alerts: unreviewed. No stated window, no dismissal reason."
   },
   "pass": {
    "language": "text",
    "code": "SECURITY.md: critical and exploited: 7 days; high: 30 days; medium: 60 days.\nOpen alerts: all younger than their window."
   },
   "sources": [
    "https://www.bestpractices.dev/en/criteria/0",
    "https://github.com/ossf/scorecard/blob/main/docs/checks.md",
    "https://www.cisa.gov/news-events/directives/bod-22-01-reducing-significant-risk-known-exploited-vulnerabilities",
    "https://docs.github.com/en/code-security/securing-your-organization/understanding-your-organizations-exposure-to-vulnerabilities/prioritizing-dependabot-alerts-using-metrics",
    "https://cheatsheetseries.owasp.org/cheatsheets/Vulnerable_Dependency_Management_Cheat_Sheet.html"
   ],
   "orgs": [
    {
     "id": "openssf",
     "name": "OpenSSF",
     "sources": [
      "https://www.bestpractices.dev/en/criteria/0",
      "https://github.com/ossf/scorecard/blob/main/docs/checks.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cisa",
     "name": "CISA",
     "sources": [
      "https://www.cisa.gov/news-events/directives/bod-22-01-reducing-significant-risk-known-exploited-vulnerabilities"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://docs.github.com/en/code-security/securing-your-organization/understanding-your-organizations-exposure-to-vulnerabilities/prioritizing-dependabot-alerts-using-metrics"
     ],
     "strength": "unjudged"
    },
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Vulnerable_Dependency_Management_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/codebase#code-vuln-remediation-window",
   "markdown_url": "https://goodcodereview.com/codebase.md"
  },
  {
   "id": "code-workflow-supply-chain",
   "level": "codebase",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security",
    "process"
   ],
   "topics": [
    "supply-chain",
    "ci"
   ],
   "lang": "core",
   "applies_when": "Auditing CI workflow files and release pipelines.",
   "context": [],
   "tool_gate": "OpenSSF Scorecard `Pinned-Dependencies` and `Dangerous-Workflow` checks; zizmor `unpinned-uses` audit.",
   "note": "GitHub, OpenSSF and StepSecurity back the pin; GitHub, OpenSSF (Scorecard `Dangerous-Workflow`) and GitLab (protected runners keep untrusted code off deployment keys) back the untrusted-code boundary; OWASP's CI/CD cheat sheet is a reference only",
   "lacks": null,
   "rule": "Pin every CI action to a full commit SHA, and keep code from an unreviewed change out of any job that holds secrets or can deploy.",
   "why": "The pipeline holds secrets and publish rights, so a compromised action or injected input owns the artifact. A privileged trigger that checks out a fork runs the fork's code with the base repository's token and secrets. Give workflows least-privilege tokens, keep untrusted event data out of `run:` and out of `pull_request_target` checkouts, and attach build provenance to releases. Review a change to the workflow file like any other code, because it can grant the next job anything. [[code-dependency-pinning]] covers dependency pins, and [[code-dependency-licence-sbom]] covers the inventory.",
   "counterpoint": "\"SHA pinning blocks automatic fixes and adds update churn.\" Let an update bot propose SHA bumps, so fixes still arrive as reviewed changes. Where the platform offers a lockfile for workflow dependencies, that lockfile is stronger than SHA pins and meets this check. A `pull_request_target` job can be safe when it never checks out or runs the fork's code, but splitting the privileged job from the untrusted one is easier to review than keeping them together.",
   "fail": {
    "language": "yaml",
    "code": "on: pull_request_target\npermissions: write-all\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4        # tag, can be moved to other code\n        with:\n          ref: ${{ github.event.pull_request.head.sha }}\n      - run: ./build.sh                  # runs the PR author's code with the base repo's token"
   },
   "pass": {
    "language": "yaml",
    "code": "on: pull_request\npermissions:\n  contents: read\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@<40-char-sha>   # v4.2.2\n      - uses: some/action@<40-char-sha>        # v3.1.0\n  deploy:\n    if: github.ref == 'refs/heads/main'      # never runs for a fork or an unmerged change\n    needs: build\n    runs-on: ubuntu-latest\n    environment: production                  # protected environment: required reviewers, scoped secrets\n    steps:\n      - run: ./deploy.sh"
   },
   "sources": [
    "https://github.com/ossf/scorecard/blob/main/docs/checks.md",
    "https://slsa.dev/spec/v1.0/levels",
    "https://owasp.org/Top10/2025/A03_2025-Software_Supply_Chain_Failures/",
    "https://docs.github.com/en/actions/reference/security/secure-use",
    "https://www.stepsecurity.io/blog/pinning-github-actions-for-enhanced-security-a-complete-guide",
    "https://cheatsheetseries.owasp.org/cheatsheets/CI_CD_Security_Cheat_Sheet.html",
    "https://docs.gitlab.com/ci/pipelines/"
   ],
   "orgs": [
    {
     "id": "openssf",
     "name": "OpenSSF",
     "sources": [
      "https://github.com/ossf/scorecard/blob/main/docs/checks.md",
      "https://slsa.dev/spec/v1.0/levels"
     ],
     "strength": "unjudged"
    },
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://owasp.org/Top10/2025/A03_2025-Software_Supply_Chain_Failures/",
      "https://cheatsheetseries.owasp.org/cheatsheets/CI_CD_Security_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://docs.github.com/en/actions/reference/security/secure-use"
     ],
     "strength": "unjudged"
    },
    {
     "id": "stepsecurity",
     "name": "StepSecurity",
     "sources": [
      "https://www.stepsecurity.io/blog/pinning-github-actions-for-enhanced-security-a-complete-guide"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://docs.gitlab.com/ci/pipelines/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/codebase#code-workflow-supply-chain",
   "markdown_url": "https://goodcodereview.com/codebase.md"
  },
  {
   "id": "code-workload-identity",
   "level": "codebase",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "security"
   ],
   "topics": [
    "supply-chain",
    "ci",
    "credentials"
   ],
   "lang": "core",
   "applies_when": "A CI job or a service authenticates to a cloud provider.",
   "context": [],
   "tool_gate": null,
   "note": "exchange for a short-lived credential backed by GitHub (no long-lived AWS secrets), Google (federated identities instead of a service account key) and Microsoft Entra (federation without managing secrets); pin the trust policy backed by GitHub (at least one condition, evaluate `sub`), Google (attribute condition) and GitLab (claims as condition keys in trust policies); GitLab's ID token page describes the token and its claims and does not argue against stored secrets",
   "lacks": null,
   "rule": "Exchange the platform's OIDC token for a short-lived cloud credential, and pin the trust policy to the workload.",
   "why": "A cloud key stored as a CI secret stays valid until someone rotates it, and every job that reads it can use it elsewhere. An OIDC token is minted for one job, expires with it, and names the repository, branch or environment. The cloud side has to check that name. A trust policy with no `sub` condition lets any repository assume the role.",
   "counterpoint": "\"Static keys work with every provider and every runner.\" Some providers and self-hosted setups cannot issue an OIDC token, so a scoped key is the fallback. If a key is unavoidable, scope it to the one deploy action, keep it in the platform's secret store rather than in the repository, rotate it, and keep the deploy job away from pull request code. Where OIDC is available, use it and pin the claim.",
   "fail": {
    "language": "yaml",
    "code": "env:\n  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}\n  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}"
   },
   "pass": {
    "language": "yaml",
    "code": "on:\n  push:\n    branches: [main]\npermissions:\n  id-token: write\n  contents: read\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: aws-actions/configure-aws-credentials@<40-char-sha>\n        with:\n          role-to-assume: arn:aws:iam::123456789012:role/deploy\n          aws-region: eu-west-1\n# trust policy condition:\n# token.actions.githubusercontent.com:sub: repo:org/repo:ref:refs/heads/main\n# repositories created after 15 July 2026, or opted in, use immutable IDs:\n# repo:org@123456/repo@456789:ref:refs/heads/main"
   },
   "sources": [
    "https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-aws",
    "https://cloud.google.com/iam/docs/workload-identity-federation",
    "https://docs.gitlab.com/ci/secrets/id_token_authentication/",
    "https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation"
   ],
   "orgs": [
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-aws"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://cloud.google.com/iam/docs/workload-identity-federation"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://docs.gitlab.com/ci/secrets/id_token_authentication/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/codebase#code-workload-identity",
   "markdown_url": "https://goodcodereview.com/codebase.md"
  },
  {
   "id": "fn-aria-role-has-state-and-keys",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "accessibility"
   ],
   "topics": [
    "frontend",
    "aria",
    "keyboard"
   ],
   "lang": "core",
   "applies_when": "A custom widget sets `role` or an `aria-*` attribute because no native element fits.",
   "context": [
    "ui"
   ],
   "tool_gate": "ESLint `jsx-a11y/role-has-required-aria-props`; axe `aria-required-attr`. Neither checks that the keys work.",
   "note": "state clause: W3C, Mozilla, Deque, Heydon Pickering; keys clause: W3C, Mozilla, Google web.dev; the accessible name has 2 orgs (W3C, Deque, both WCAG 4.1.2) and sits in Why; tool-gate rule names checked against the jsx-a11y README and the axe rule list",
   "lacks": null,
   "rule": "When a native element cannot do the job, give the custom widget the state attribute and the keys its role promises.",
   "why": "A role only tells assistive technology what the element is. It adds no keyboard behaviour, and it says nothing about state. A `role=\"switch\"` with no `aria-checked` is announced as a switch of unknown state. Wrong ARIA is worse than none, because it gives a screen-reader user a false picture. The widget also needs an accessible name, from its text or a label, or it is announced by role alone.",
   "counterpoint": "\"ARIA patterns are in the guide, so we copy them.\" Copy the keyboard table as well as the attributes. For a plain toggle, a `button` with `aria-pressed` is enough, and the label should stay the same when the state changes.",
   "fail": {
    "language": "jsx",
    "code": "<button type=\"button\" role=\"switch\" onClick={toggle}>Dark mode</button>\n// no aria-checked: announced as a switch of unknown state"
   },
   "pass": {
    "language": "jsx",
    "code": "<button type=\"button\" role=\"switch\" aria-checked={on} onClick={toggle}>\n  Dark mode\n</button>\n// a native <button> already takes Tab, Enter, and Space"
   },
   "sources": [
    "https://www.w3.org/WAI/ARIA/apg/practices/read-me-first/",
    "https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html",
    "https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Accessibility/HTML",
    "https://inclusive-components.design/toggle-button/",
    "https://www.w3.org/WAI/ARIA/apg/patterns/switch/",
    "https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/switch_role",
    "https://dequeuniversity.com/rules/axe/html/4.10/aria-required-attr",
    "https://web.dev/learn/accessibility/aria-html"
   ],
   "orgs": [
    {
     "id": "w3c",
     "name": "W3C",
     "sources": [
      "https://www.w3.org/WAI/ARIA/apg/practices/read-me-first/",
      "https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html",
      "https://www.w3.org/WAI/ARIA/apg/patterns/switch/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Accessibility/HTML",
      "https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/switch_role"
     ],
     "strength": "unjudged"
    },
    {
     "id": "deque",
     "name": "Deque",
     "sources": [
      "https://dequeuniversity.com/rules/axe/html/4.10/aria-required-attr"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://web.dev/learn/accessibility/aria-html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "heydon",
     "name": "Heydon Pickering",
     "sources": [
      "https://inclusive-components.design/toggle-button/"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-aria-role-has-state-and-keys",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-bool-arg-at-callsite",
   "level": "function",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "readability",
    "api"
   ],
   "lang": "core",
   "applies_when": "A call passes a literal `True` or `False` for a parameter.",
   "context": [],
   "tool_gate": "ruff FBT",
   "note": "from the author's own Python review guide",
   "lacks": null,
   "rule": "Replace a boolean flag argument with an enum, keyword-only argument or two functions.",
   "why": "`render(doc, True, False)` says nothing. A boolean also hides that a third state is coming, and when it does, every caller changes.",
   "counterpoint": "\"A keyword flag is fine and shorter.\" A named keyword-only flag is acceptable when it is truly binary and will stay so. The FAIL is the positional literal, which is why the severity is discuss.",
   "fail": {
    "language": "text",
    "code": "export(report, true, false)"
   },
   "pass": {
    "language": "text",
    "code": "export(report, fmt=Format.CSV, header=Header.OMIT)"
   },
   "sources": [
    "https://kotlinlang.org/docs/coding-conventions.html",
    "https://www.swift.org/documentation/api-design-guidelines/",
    "https://docs.astral.sh/ruff/rules/boolean-positional-value-in-call/"
   ],
   "orgs": [
    {
     "id": "jetbrains",
     "name": "JetBrains",
     "sources": [
      "https://kotlinlang.org/docs/coding-conventions.html"
     ],
     "strength": "supports"
    },
    {
     "id": "swift",
     "name": "Swift project",
     "sources": [
      "https://www.swift.org/documentation/api-design-guidelines/"
     ],
     "strength": "supports"
    },
    {
     "id": "astral",
     "name": "Astral",
     "sources": [
      "https://docs.astral.sh/ruff/rules/boolean-positional-value-in-call/"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "strong",
   "url": "https://goodcodereview.com/function#fn-bool-arg-at-callsite",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-bounded-read-fails-at-cap",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "correctness",
    "reliability",
    "security"
   ],
   "topics": [
    "io"
   ],
   "lang": "core",
   "applies_when": "A function reads a file, a response or a request body through a reader that has a byte limit.",
   "context": [],
   "tool_gate": "Go lint rule against `io.LimitReader` on untrusted input; a test that feeds one byte more than the cap and expects an error.",
   "note": "Google (Go) states the difference between a limit that ends with EOF and one that errors. Django raises an error at its cap, IETF defines 413 for a body over the cap, and OWASP says to reject instead of continuing with partial data. Python and Java readers that cut at n are not cited",
   "lacks": null,
   "rule": "When a size cap is reached, return an error; never hand back the first N bytes as if they were the whole input.",
   "why": "A reader that stops at the cap reports a normal end of input. The code then parses half a JSON document, saves a truncated file, or hashes a prefix, and nothing says the data was cut. The failure appears later as a corrupt record. Check for bytes beyond the cap, or use a reader that errors.",
   "counterpoint": "\"We only need the first part, like a preview.\" Then say so in the name and the return value, such as `readPrefix` returning a `truncated` flag. The card fails a function that presents the prefix as the full content.",
   "fail": {
    "language": "go",
    "code": "body, err := io.ReadAll(io.LimitReader(resp.Body, 16<<20)) // 17 MiB payload: err == nil, body cut at 16 MiB\nif err != nil {\n\treturn nil, err\n}\nreturn body, nil"
   },
   "pass": {
    "language": "go",
    "code": "body, err := io.ReadAll(io.LimitReader(resp.Body, 16<<20+1))\nif err != nil {\n\treturn nil, err\n}\nif len(body) > 16<<20 {\n\treturn nil, fmt.Errorf(\"response exceeds %d bytes\", 16<<20)\n}\nreturn body, nil"
   },
   "sources": [
    "https://pkg.go.dev/net/http#MaxBytesReader",
    "https://pkg.go.dev/io#LimitReader",
    "https://docs.djangoproject.com/en/5.2/ref/settings/#data-upload-max-memory-size",
    "https://www.rfc-editor.org/rfc/rfc9110.html#name-413-content-too-large",
    "https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html",
    "https://github.com/NVIDIA/aicr/blob/main/AGENTS.md"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://pkg.go.dev/net/http#MaxBytesReader",
      "https://pkg.go.dev/io#LimitReader"
     ],
     "strength": "unjudged"
    },
    {
     "id": "django",
     "name": "Django Software Foundation",
     "sources": [
      "https://docs.djangoproject.com/en/5.2/ref/settings/#data-upload-max-memory-size"
     ],
     "strength": "unjudged"
    },
    {
     "id": "ietf",
     "name": "IETF",
     "sources": [
      "https://www.rfc-editor.org/rfc/rfc9110.html#name-413-content-too-large"
     ],
     "strength": "unjudged"
    },
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nvidia",
     "name": "NVIDIA",
     "sources": [
      "https://github.com/NVIDIA/aicr/blob/main/AGENTS.md"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/function#fn-bounded-read-fails-at-cap",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-colour-not-sole-cue",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "accessibility",
    "usability"
   ],
   "topics": [
    "frontend",
    "css"
   ],
   "lang": "core",
   "applies_when": "Styles or markup show an error, a required field, a status, or a link using colour.",
   "context": [
    "ui"
   ],
   "tool_gate": "none; view the page in greyscale, or run a browser colour-vision emulation.",
   "note": null,
   "lacks": null,
   "rule": "Pair every colour that carries meaning with a second cue: text, an icon, a shape, or an underline.",
   "why": "Colour-blind users, glare, greyscale mode, and a screen reader all remove colour. A red border on a bad field, or a link that differs from body text only by hue, tells those users nothing.",
   "counterpoint": "\"Colour is the quickest cue, and the icon clutters the design.\" Colour can stay as the fast cue. The rule asks for a second one, not for removing the first. A text prefix such as \"Error:\" costs a few characters. A link in body text can also pass with 3:1 contrast against the text around it plus a non-colour cue on hover and focus. The underline is the simpler fix.",
   "fail": {
    "language": "css",
    "code": ".field.error { border-color: red; }\n.status-ok   { color: green; }\na { color: #0a58ca; text-decoration: none; }   /* no underline, no hover or focus cue */"
   },
   "pass": {
    "language": "html",
    "code": "<input aria-invalid=\"true\" aria-describedby=\"e1\" style=\"border: 2px solid #b42318\">\n<p id=\"e1\" class=\"error\"><svg aria-hidden=\"true\">…</svg> Error: enter a date such as 27/03/2026</p>\n<a href=\"/terms\" style=\"text-decoration: underline\">terms</a>"
   },
   "sources": [
    "https://www.w3.org/WAI/WCAG22/Understanding/use-of-color.html",
    "https://www.nngroup.com/articles/error-message-guidelines/",
    "https://developer.apple.com/tutorials/data/design/human-interface-guidelines/accessibility.json",
    "https://inclusive-components.design/notifications/"
   ],
   "orgs": [
    {
     "id": "w3c",
     "name": "W3C",
     "sources": [
      "https://www.w3.org/WAI/WCAG22/Understanding/use-of-color.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nngroup",
     "name": "Nielsen Norman Group",
     "sources": [
      "https://www.nngroup.com/articles/error-message-guidelines/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "apple",
     "name": "Apple",
     "sources": [
      "https://developer.apple.com/tutorials/data/design/human-interface-guidelines/accessibility.json"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "heydon",
     "name": "Heydon Pickering",
     "sources": [
      "https://inclusive-components.design/notifications/"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-colour-not-sole-cue",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-comments-explain-why",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "readability",
    "docs"
   ],
   "lang": "core",
   "applies_when": "A comment sits next to code, or the code needs one to be understood.",
   "context": [],
   "tool_gate": null,
   "note": "4 orgs back the Rule (Google, Mozilla, Apache Flink; PEP 8 agrees). Google, the Linux kernel and Refactoring.Guru say to rewrite before commenting, so that point sits in the Why",
   "lacks": null,
   "rule": "Write comments that give a reason, a constraint, or a trade-off, and never restate what the next line does.",
   "why": "A restating comment rots silently and adds nothing. A missing reason makes the next person delete the code that mattered. Rename or extract a step before you comment how the code works.",
   "counterpoint": "\"Good names make comments unnecessary.\" Names carry *what*. They cannot carry why a branch exists, a unit, or what callers may rely on, and a missing reason costs more than a slightly stale one. Rename first, then comment what the name cannot say. A stale comment is a defect either way. The test is whether the comment adds information that the code cannot carry.",
   "fail": {
    "language": "text",
    "code": "counter += 1  # increment the counter\nretry = False  # set retry to False"
   },
   "pass": {
    "language": "text",
    "code": "# Business decision: activation and entitlement update in one transaction,\n# so a user never has access without an active subscription.\n# TODO: remove when billing emits entitlement events itself."
   },
   "sources": [
    "https://dev.to/sportebois/software-design-red-flags-wisdom-nuggets-from-john-ousterhout-43i2",
    "https://google.github.io/eng-practices/review/reviewer/looking-for.html",
    "https://github.com/johnousterhout/aposd-vs-clean-code/blob/main/README.md",
    "https://henrikwarne.com/2024/01/10/tidy-first/",
    "https://developer.mozilla.org/en-US/docs/MDN/Writing_guidelines/Code_style_guide/JavaScript",
    "https://peps.python.org/pep-0008/",
    "https://flink.apache.org/how-to-contribute/code-style-and-quality-common/",
    "https://www.kernel.org/doc/html/latest/process/coding-style.html",
    "https://refactoring.guru/smells/comments"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://google.github.io/eng-practices/review/reviewer/looking-for.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://developer.mozilla.org/en-US/docs/MDN/Writing_guidelines/Code_style_guide/JavaScript"
     ],
     "strength": "unjudged"
    },
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://peps.python.org/pep-0008/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "apache",
     "name": "Apache Software Foundation",
     "sources": [
      "https://flink.apache.org/how-to-contribute/code-style-and-quality-common/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "kernel",
     "name": "Linux kernel",
     "sources": [
      "https://www.kernel.org/doc/html/latest/process/coding-style.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "refactoring-guru",
     "name": "Refactoring.Guru",
     "sources": [
      "https://refactoring.guru/smells/comments"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "portebois",
     "name": "Sébastien Portebois",
     "sources": [
      "https://dev.to/sportebois/software-design-red-flags-wisdom-nuggets-from-john-ousterhout-43i2"
     ],
     "strength": "unjudged"
    },
    {
     "id": "ousterhout",
     "name": "John Ousterhout",
     "sources": [
      "https://github.com/johnousterhout/aposd-vs-clean-code/blob/main/README.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "warne",
     "name": "Henrik Warne",
     "sources": [
      "https://henrikwarne.com/2024/01/10/tidy-first/"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-comments-explain-why",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-comments-survive-context",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "docs"
   ],
   "lang": "core",
   "applies_when": "A comment or TODO says \"currently\", \"for now\", \"not yet\", \"see the PR\" or promises future work.",
   "context": [],
   "tool_gate": null,
   "note": "Google C++, Python and Java guides say a future-dated TODO needs a specific date or event. They also want a bug link in the TODO, so the card allows ticket ids; the Ousterhout summary stays as a reference",
   "lacks": null,
   "rule": "Write comments that remain true without outside context: no \"currently\", \"for now\" or \"see the PR\", and give a TODO the date or event that ends it.",
   "why": "The PR vanishes from memory and the \"currently\" never gets updated. A comment whose context has expired is a lie by the next release. A TODO with a removal condition tells the next reader when to act. A bug link in the TODO is welcome, as long as the comment says what to do and when.",
   "counterpoint": "\"Ticket links help traceability.\" They do, and the PASS keeps one. The ticket alone is not enough, since trackers get migrated and closed. Name the condition in the comment too.",
   "fail": {
    "language": "text",
    "code": "# TODO: handle the legacy case for now\nretry = False  # see the PR, currently disabled"
   },
   "pass": {
    "language": "text",
    "code": "# TODO(JIRA-4412): drop this branch once every client sends v2 payloads"
   },
   "sources": [
    "https://google.github.io/styleguide/cppguide.html",
    "https://google.github.io/styleguide/pyguide.html",
    "https://google.github.io/styleguide/javaguide.html",
    "https://dev.to/sportebois/software-design-red-flags-wisdom-nuggets-from-john-ousterhout-43i2",
    "https://docs.webkit.org/Deep%20Dive/Web%20Inspector/Compatibility.html",
    "https://github.com/PostHog/posthog/blob/master/.agents/skills/writing-code-comments/SKILL.md"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://google.github.io/styleguide/cppguide.html",
      "https://google.github.io/styleguide/pyguide.html",
      "https://google.github.io/styleguide/javaguide.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "webkit",
     "name": "WebKit",
     "sources": [
      "https://docs.webkit.org/Deep%20Dive/Web%20Inspector/Compatibility.html"
     ],
     "strength": "supports"
    },
    {
     "id": "posthog",
     "name": "PostHog",
     "sources": [
      "https://github.com/PostHog/posthog/blob/master/.agents/skills/writing-code-comments/SKILL.md"
     ],
     "strength": "supports"
    }
   ],
   "people": [
    {
     "id": "portebois",
     "name": "Sébastien Portebois",
     "sources": [
      "https://dev.to/sportebois/software-design-red-flags-wisdom-nuggets-from-john-ousterhout-43i2"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "mixed",
   "url": "https://goodcodereview.com/function#fn-comments-survive-context",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-confine-file-path",
   "level": "function",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "path-traversal",
    "files"
   ],
   "lang": "core",
   "applies_when": "Code opens, reads, writes or deletes a file whose name or path includes request data.",
   "context": [],
   "tool_gate": "CodeQL `py/path-injection` (CWE-22) or the equivalent query for your language.",
   "note": null,
   "lacks": null,
   "rule": "Resolve the final path and check that it stays inside the intended base directory before using it.",
   "why": "A name like `../../etc/passwd`, or its percent-encoded form, walks out of the upload folder. Stripping `../` fails against `....//` and double encoding. The reliable check works on the resolved path. Better still, map a user choice to an ID and build the path in code.",
   "counterpoint": "\"We already strip `..`.\" String stripping is a blocklist and gets bypassed. Resolve first, compare second. Resolving catches a symlink that already points outside, but a symlink swapped in between the check and the open still escapes. For trees that untrusted users can write to, use a rooted open such as Go's `os.OpenInRoot`, which refuses any escape at open time.",
   "fail": {
    "language": "python",
    "code": "return send_file(os.path.join(UPLOADS, request.args[\"name\"]))"
   },
   "pass": {
    "language": "python",
    "code": "p = (UPLOADS / request.args[\"name\"]).resolve()\nif not p.is_relative_to(UPLOADS.resolve()): abort(400)\nreturn send_file(p)"
   },
   "sources": [
    "https://community.owasp.org/attacks/Path_Traversal",
    "https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html",
    "https://go.dev/blog/osroot",
    "https://codeql.github.com/codeql-query-help/python/py-path-injection/"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://community.owasp.org/attacks/Path_Traversal",
      "https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://go.dev/blog/osroot"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://codeql.github.com/codeql-query-help/python/py-path-injection/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-confine-file-path",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-constant-time-secret-compare",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "security"
   ],
   "topics": [
    "crypto"
   ],
   "lang": "core",
   "applies_when": "Code compares a caller-supplied API key, token, signature, MAC or CSRF value with a stored or computed secret.",
   "context": [],
   "tool_gate": "Semgrep or CodeQL rules for `==` on secrets; Bandit has no rule for this, so grep for `==` next to names such as `token`, `signature`, `api_key`, `digest`.",
   "note": "fn-csprng-for-secrets covers generating the value; this card covers checking it. A password hash check goes through the hash library's verify call (fn-password-slow-hash)",
   "lacks": null,
   "rule": "Compare a secret, token or MAC with the platform's constant-time function, never with `==` or `!=`.",
   "why": "Ordinary equality stops at the first differing byte, so the response time tells an attacker how many leading bytes were right. Repeated requests recover the value one byte at a time. The constant-time call costs one line and no performance anyone will notice.",
   "counterpoint": "\"Network jitter hides the difference.\" Attackers average thousands of samples, and the jitter shrinks when they sit in the same cloud region. Also, some constant-time functions need equal lengths (Node throws otherwise); compare fixed-length digests, or check the length first, since the length of a MAC is not secret.",
   "fail": {
    "language": "python",
    "code": "def verify(body: bytes, header_sig: str, key: bytes) -> bool:\n    expected = hmac.new(key, body, \"sha256\").hexdigest()\n    return expected == header_sig"
   },
   "pass": {
    "language": "python",
    "code": "def verify(body: bytes, header_sig: str, key: bytes) -> bool:\n    expected = hmac.new(key, body, \"sha256\").hexdigest()\n    return hmac.compare_digest(expected.encode(), header_sig.encode())   # bytes: a non-ASCII header raises no TypeError\n# Go: subtle.ConstantTimeCompare(a, b) == 1\n# Node: crypto.timingSafeEqual(bufA, bufB) after checking the lengths match"
   },
   "sources": [
    "https://docs.python.org/3/library/hmac.html",
    "https://pkg.go.dev/crypto/subtle",
    "https://nodejs.org/api/crypto.html",
    "https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries",
    "https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html",
    "https://cwe.mitre.org/data/definitions/208.html"
   ],
   "orgs": [
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://docs.python.org/3/library/hmac.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://pkg.go.dev/crypto/subtle"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nodejs",
     "name": "OpenJS Foundation",
     "sources": [
      "https://nodejs.org/api/crypto.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries"
     ],
     "strength": "unjudged"
    },
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mitre",
     "name": "MITRE",
     "sources": [
      "https://cwe.mitre.org/data/definitions/208.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-constant-time-secret-compare",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-csprng-for-secrets",
   "level": "function",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "crypto"
   ],
   "lang": "core",
   "applies_when": "Code generates a token, session ID, password-reset code, API key, nonce or IV.",
   "context": [],
   "tool_gate": "Bandit B311 (`random`) for Python; elsewhere a grep for `Math.random` and `rand()` in code that builds tokens or keys.",
   "note": null,
   "lacks": null,
   "rule": "Draw every security-relevant random value from the platform CSPRNG.",
   "why": "A general-purpose PRNG is predictable from a few outputs, so an attacker who sees two tokens can guess the next. UUIDv1 is time-based and also not secret. The API call is the whole fix, so it is cheap to demand.",
   "counterpoint": "\"Math.random is fine for a short-lived code.\" Short-lived codes still get brute-forced, and predictable ones need no brute force. Use the CSPRNG for anything an attacker gains from guessing.",
   "fail": {
    "language": "python",
    "code": "token = \"\".join(random.choices(string.ascii_letters, k=32))\nreset = str(int(time.time()))"
   },
   "pass": {
    "language": "python",
    "code": "token = secrets.token_urlsafe(32)           # os.urandom under the hood\n# JavaScript: crypto.getRandomValues(new Uint8Array(32))"
   },
   "sources": [
    "https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html",
    "https://top10.owasp.org/2025/A04_2025-Cryptographic_Failures",
    "https://docs.python.org/3/library/secrets.html",
    "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random",
    "https://www.rfc-editor.org/rfc/rfc4086.html",
    "https://pkg.go.dev/crypto/rand"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html",
      "https://top10.owasp.org/2025/A04_2025-Cryptographic_Failures"
     ],
     "strength": "unjudged"
    },
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://docs.python.org/3/library/secrets.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random"
     ],
     "strength": "unjudged"
    },
    {
     "id": "ietf",
     "name": "IETF",
     "sources": [
      "https://www.rfc-editor.org/rfc/rfc4086.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://pkg.go.dev/crypto/rand"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-csprng-for-secrets",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-destructive-confirm-or-undo",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "usability"
   ],
   "topics": [
    "frontend",
    "forms"
   ],
   "lang": "core",
   "applies_when": "A handler deletes user data, spends money, or sends something that cannot be recalled.",
   "context": [
    "ui"
   ],
   "tool_gate": "none; a test that triggers the action and asserts a confirm step or an undo path exists.",
   "note": "4 orgs (NN/g, Apple, W3C, Shneiderman) for undo or confirm. Naming the outcome on the buttons and not defaulting to Cancel has 2 (NN/g, Apple), so it is in Why. Norman's undo chapter is a table of contents only, so topic only",
   "lacks": null,
   "rule": "Make a destructive action reversible with undo, or ask the user to confirm it before it runs.",
   "why": "People click the wrong thing. If the action cannot be reversed and nothing stops it, one slip deletes their work. Undo costs a successful user nothing, while a dialog on every click trains them to click through. A dialog works best when its buttons name the outcome, such as \"Delete project\" and \"Cancel\", because \"OK\" and \"Are you sure?\" say nothing about what happens next.",
   "counterpoint": "\"Confirm everything to be safe.\" Frequent prompts are dismissed without reading, so they protect nothing. Use undo for common actions and reserve the dialog for rare, irreversible ones. Do not make Cancel the default button.",
   "fail": {
    "language": "jsx",
    "code": "<button onClick={() => api.deleteProject(id)}>Delete</button>"
   },
   "pass": {
    "language": "jsx",
    "code": "async function remove() {\n  await api.softDelete(id);\n  toast(\"Project deleted\", { action: { label: \"Undo\", onClick: () => api.restore(id) } });\n}\n// irreversible case: a dialog \"Delete project “Q3 plan”?\" with buttons \"Delete project\" and \"Cancel\""
   },
   "sources": [
    "https://www.nngroup.com/articles/confirmation-dialog/",
    "https://www.nngroup.com/articles/ten-usability-heuristics/",
    "https://developer.apple.com/tutorials/data/design/human-interface-guidelines/alerts.json",
    "https://www.w3.org/WAI/WCAG22/Understanding/error-prevention-legal-financial-data.html",
    "https://www.cs.umd.edu/users/ben/goldenrules.html"
   ],
   "orgs": [
    {
     "id": "nngroup",
     "name": "Nielsen Norman Group",
     "sources": [
      "https://www.nngroup.com/articles/confirmation-dialog/",
      "https://www.nngroup.com/articles/ten-usability-heuristics/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "apple",
     "name": "Apple",
     "sources": [
      "https://developer.apple.com/tutorials/data/design/human-interface-guidelines/alerts.json"
     ],
     "strength": "unjudged"
    },
    {
     "id": "w3c",
     "name": "W3C",
     "sources": [
      "https://www.w3.org/WAI/WCAG22/Understanding/error-prevention-legal-financial-data.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "shneiderman",
     "name": "Ben Shneiderman",
     "sources": [
      "https://www.cs.umd.edu/users/ben/goldenrules.html"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-destructive-confirm-or-undo",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-dialog-focus-managed",
   "level": "function",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "accessibility",
    "usability"
   ],
   "topics": [
    "frontend",
    "keyboard"
   ],
   "lang": "core",
   "applies_when": "A component opens a modal dialog, drawer, or menu that covers the page.",
   "context": [
    "ui"
   ],
   "tool_gate": "none; a component test that opens the dialog, presses Tab and Escape, and asserts `document.activeElement`.",
   "note": "Focus into the modal, Tab kept inside: W3C APG, Mozilla, GitHub (Primer). Escape and return focus to the opener are in W3C APG and WebAIM; MDN does not state return focus, so that part is in Why",
   "lacks": null,
   "rule": "When a modal opens, move focus into it and keep Tab inside it. Make Escape close it.",
   "why": "Without it, a keyboard user keeps tabbing through the page behind the dialog, or gets stuck inside it with no exit. When it closes, return focus to the control that opened it. Focus that falls to the top of the page makes them start over.",
   "counterpoint": "\"Our library handles it.\" Check, because a library that traps focus but never restores it is common. Prefer the native `<dialog>` with `showModal()`, which makes the page inert and closes on Escape. A dialog built from generic elements must recreate each of those.",
   "fail": {
    "language": "jsx",
    "code": "{open && <div className=\"modal\"><h2>Delete?</h2><button onClick={del}>Delete</button></div>}"
   },
   "pass": {
    "language": "html",
    "code": "<button id=\"opener\">Delete project</button>\n<dialog id=\"dlg\" aria-labelledby=\"t\"><h2 id=\"t\">Delete project?</h2>\n  <button autofocus>Cancel</button> <button>Delete</button></dialog>\n<script>\n  const opener = document.getElementById(\"opener\"), dlg = document.getElementById(\"dlg\");\n  opener.onclick = () => dlg.showModal();   // focus moves in, page behind is inert\n  dlg.onclose = () => opener.focus();       // focus returns to the opener\n</script>"
   },
   "sources": [
    "https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/",
    "https://www.w3.org/WAI/WCAG22/Understanding/no-keyboard-trap.html",
    "https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog",
    "https://webaim.org/techniques/keyboard/",
    "https://primer.style/product/components/dialog/accessibility"
   ],
   "orgs": [
    {
     "id": "w3c",
     "name": "W3C",
     "sources": [
      "https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/",
      "https://www.w3.org/WAI/WCAG22/Understanding/no-keyboard-trap.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog"
     ],
     "strength": "unjudged"
    },
    {
     "id": "webaim",
     "name": "WebAIM",
     "sources": [
      "https://webaim.org/techniques/keyboard/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://primer.style/product/components/dialog/accessibility"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-dialog-focus-managed",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-disclosure-hides-only-secondary",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "usability"
   ],
   "topics": [
    "frontend",
    "html"
   ],
   "lang": "core",
   "applies_when": "Markup or a component hides content behind a `<details>`, an accordion, a \"Show more\" link, or an \"Advanced\" toggle.",
   "context": [
    "ui"
   ],
   "tool_gate": null,
   "note": "Not hiding what most users need: NN/g, GOV.UK, Appian. No organisation states that a closed disclosure must not hide the primary action, so that part is out of the Rule. The two-level cap is NN/g's alone, so it sits in Why",
   "lacks": null,
   "rule": "Do not hide a required field or information most users need behind a disclosure that starts closed.",
   "why": "Users who never open the disclosure must still be able to finish the task. A closed `<details>` that holds the delivery date, or an \"Advanced\" section that holds a required field, sends most users hunting. Disclosure is for what only some users need. A field that appears because of the user's own answer, such as a follow-up field shown after they pick \"Yes\", is a conditional reveal, not a disclosure, and passes. Nesting adds its own cost. NN/g counts the first view as level one, so a disclosure inside a disclosure is already level three, and NN/g found designs past two levels lose people as they move between levels. In a step-by-step flow, such as picking a make and then a model, Appian says to disable fields that are not ready yet rather than hide them.",
   "counterpoint": "\"Hiding fields makes the form shorter.\" It makes the first view shorter, but required content still has to be found. Measure who opens the disclosure. If most users open it, the content belongs on the default view.",
   "fail": {
    "language": "html",
    "code": "<details>\n  <summary>Delivery options</summary>\n  <label for=\"address\">Delivery address</label>\n  <input type=\"text\" id=\"address\" required>\n</details>"
   },
   "pass": {
    "language": "html",
    "code": "<label for=\"address\">Delivery address</label>\n<input type=\"text\" id=\"address\" required>\n<details>\n  <summary>Add an order reference</summary>\n  <label for=\"ref\">Order reference</label>\n  <input type=\"text\" id=\"ref\">\n</details>"
   },
   "sources": [
    "https://www.nngroup.com/articles/progressive-disclosure/",
    "https://design-system.service.gov.uk/components/details/",
    "https://docs.appian.com/suite/help/26.9/sail/ux-progressive-disclosure.html"
   ],
   "orgs": [
    {
     "id": "nngroup",
     "name": "Nielsen Norman Group",
     "sources": [
      "https://www.nngroup.com/articles/progressive-disclosure/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "govuk",
     "name": "GOV.UK",
     "sources": [
      "https://design-system.service.gov.uk/components/details/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "appian",
     "name": "Appian",
     "sources": [
      "https://docs.appian.com/suite/help/26.9/sail/ux-progressive-disclosure.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-disclosure-hides-only-secondary",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-distinct-types-for-ids",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "correctness"
   ],
   "topics": [
    "types"
   ],
   "lang": "core",
   "applies_when": "A function takes two or more arguments of one primitive type that mean different things (ids, amounts, units).",
   "context": [],
   "tool_gate": "Type checker with a newtype or branded-type pattern.",
   "note": "one vendor's engineering guidance (opinion), from pstack principle-type-system-discipline",
   "lacks": null,
   "rule": "Give arguments with different meanings distinct types, so a call that swaps them fails to type-check.",
   "why": "Two `str` ids compile in either order. The swap passes review and fails in production, often as a \"not found\" that looks like missing data.",
   "counterpoint": "\"Wrapper types add ceremony.\" A newtype costs one line and no runtime. Where the types differ only by argument position and the function has two arguments with obvious names, keyword-only parameters can be enough.",
   "fail": {
    "language": "python",
    "code": "def refund(user_id: str, order_id: str) -> None: ...\nrefund(order.id, user.id)   # accepted"
   },
   "pass": {
    "language": "python",
    "code": "UserId = NewType(\"UserId\", str)\nOrderId = NewType(\"OrderId\", str)\ndef refund(user_id: UserId, order_id: OrderId) -> None: ..."
   },
   "sources": [
    "https://github.com/cursor/plugins/blob/main/pstack/skills/principle-type-system-discipline/SKILL.md",
    "https://rust-lang.github.io/api-guidelines/type-safety.html",
    "https://docs.python.org/3/library/typing.html#typing.NewType"
   ],
   "orgs": [
    {
     "id": "cursor",
     "name": "Cursor",
     "sources": [
      "https://github.com/cursor/plugins/blob/main/pstack/skills/principle-type-system-discipline/SKILL.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "rust",
     "name": "Rust project",
     "sources": [
      "https://rust-lang.github.io/api-guidelines/type-safety.html"
     ],
     "strength": "supports"
    },
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://docs.python.org/3/library/typing.html#typing.NewType"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/function#fn-distinct-types-for-ids",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-english-identifiers",
   "level": "function",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "naming",
    "readability"
   ],
   "lang": "core",
   "applies_when": "The project has a written language policy, or its code may be read by people outside one language group.",
   "context": [],
   "tool_gate": "Spell-check or non-ASCII identifier lint.",
   "note": "no source makes English universal; PEP 8 and LLVM give the rule, PEP 3131 the counter-view, so the card stays a project-policy check",
   "lacks": null,
   "rule": "Write identifiers and comments in English, unless the project policy names another language.",
   "why": "Mixed languages split search, break naming conventions, and exclude readers. The tools, libraries, and error messages the code touches are already in English.",
   "counterpoint": "\"Domain terms have no English word.\" Keep the local term, once, in a glossary, and comment it ([[mod-one-name-per-concept]]). Python itself allows non-ASCII identifiers so that teams can name things in their own language (PEP 3131). If the code will never leave one language group, a team may choose that language. Write the choice down so the check can pass.",
   "fail": {
    "language": "text",
    "code": "def calcular_precio_total(carrito): ...   # next to total_price() and cart.items"
   },
   "pass": {
    "language": "text",
    "code": "def calculate_total_price(cart): ..."
   },
   "sources": [
    "https://peps.python.org/pep-0008/",
    "https://peps.python.org/pep-3131/",
    "https://llvm.org/docs/CodingStandards.html",
    "https://www.gnu.org/prep/standards/standards.html",
    "https://moodledev.io/general/development/policies/codingstyle",
    "https://project.pages.drupalcode.org/coding_standards/"
   ],
   "orgs": [
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://peps.python.org/pep-0008/",
      "https://peps.python.org/pep-3131/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "llvm",
     "name": "LLVM",
     "sources": [
      "https://llvm.org/docs/CodingStandards.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gnu",
     "name": "GNU Project",
     "sources": [
      "https://www.gnu.org/prep/standards/standards.html"
     ],
     "strength": "supports"
    },
    {
     "id": "moodle",
     "name": "Moodle",
     "sources": [
      "https://moodledev.io/general/development/policies/codingstyle"
     ],
     "strength": "supports"
    },
    {
     "id": "drupal",
     "name": "Drupal",
     "sources": [
      "https://project.pages.drupalcode.org/coding_standards/"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "strong",
   "url": "https://goodcodereview.com/function#fn-english-identifiers",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-error-says-what-and-how",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "usability",
    "accessibility"
   ],
   "topics": [
    "frontend",
    "forms"
   ],
   "lang": "core",
   "applies_when": "Code builds the message shown when user input fails validation.",
   "context": [
    "ui"
   ],
   "tool_gate": "none; a rendered check that submits each invalid field.",
   "note": null,
   "lacks": null,
   "rule": "Show each validation error next to its field, in plain words that name what is wrong and how to fix it.",
   "why": "\"Invalid input\" or an error code leaves the user to guess which field and what to change. A message at the top of a long form, or in a toast that fades, sends a magnifier user searching. Naming the fix is the shortest path to a valid form.",
   "counterpoint": "\"Detailed messages leak information.\" For a login, that is true: say \"Email or password is wrong\". For ordinary fields, the specific message is the fix. Hold back the suggestion only where it would weaken security.",
   "fail": {
    "language": "html",
    "code": "<input id=\"dob\" name=\"dob\" class=\"red\">\n<div class=\"toast\">Error 422: invalid data</div>"
   },
   "pass": {
    "language": "html",
    "code": "<label for=\"dob\">Date of birth</label>\n<p id=\"dob-err\"><span class=\"visually-hidden\">Error:</span>\n   Enter a date of birth in the past, such as 27 3 1986</p>\n<input id=\"dob\" name=\"dob\" aria-invalid=\"true\" aria-describedby=\"dob-err\">"
   },
   "sources": [
    "https://www.nngroup.com/articles/ten-usability-heuristics/",
    "https://www.nngroup.com/articles/error-message-guidelines/",
    "https://design-system.service.gov.uk/components/error-message/",
    "https://design-system.service.gov.uk/components/error-summary/",
    "https://www.w3.org/WAI/WCAG22/Understanding/error-suggestion.html",
    "https://www.w3.org/WAI/tutorials/forms/notifications/",
    "https://designsystem.digital.gov/components/form/",
    "https://www.cs.umd.edu/users/ben/goldenrules.html"
   ],
   "orgs": [
    {
     "id": "nngroup",
     "name": "Nielsen Norman Group",
     "sources": [
      "https://www.nngroup.com/articles/ten-usability-heuristics/",
      "https://www.nngroup.com/articles/error-message-guidelines/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "govuk",
     "name": "GOV.UK",
     "sources": [
      "https://design-system.service.gov.uk/components/error-message/",
      "https://design-system.service.gov.uk/components/error-summary/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "w3c",
     "name": "W3C",
     "sources": [
      "https://www.w3.org/WAI/WCAG22/Understanding/error-suggestion.html",
      "https://www.w3.org/WAI/tutorials/forms/notifications/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "uswds",
     "name": "USWDS",
     "sources": [
      "https://designsystem.digital.gov/components/form/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "shneiderman",
     "name": "Ben Shneiderman",
     "sources": [
      "https://www.cs.umd.edu/users/ben/goldenrules.html"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-error-says-what-and-how",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-fetch-only-needed-data",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "performance"
   ],
   "topics": [
    "database"
   ],
   "lang": "core",
   "applies_when": "A function queries a database or an API and then filters, counts, or sums the result in application code.",
   "context": [
    "data"
   ],
   "tool_gate": null,
   "note": null,
   "lacks": null,
   "rule": "Filter, count, and aggregate in the query, and select only the columns the code uses.",
   "why": "Loading every row to count it, or every column to read one, moves data across the network and through memory for nothing. `SELECT *` also rules out an index-only scan.",
   "counterpoint": "\"Pushing logic into the database makes it the bottleneck.\" It does for formatting and business rules. Filtering and aggregation are what the database is built for, and moving them out usually ships far more data than it saves.",
   "fail": {
    "language": "python",
    "code": "total = sum(o.amount for o in Order.objects.all() if o.status == \"paid\")\nn = len(Order.objects.filter(user=u))        # loads every row\nemails = [u.email for u in User.objects.all()]   # loads every column"
   },
   "pass": {
    "language": "python",
    "code": "total = Order.objects.filter(status=\"paid\").aggregate(Sum(\"amount\"))[\"amount__sum\"]\nn = Order.objects.filter(user=u).count()\nemails = User.objects.values_list(\"email\", flat=True)"
   },
   "sources": [
    "https://learn.microsoft.com/en-us/azure/architecture/antipatterns/extraneous-fetching/",
    "https://docs.djangoproject.com/en/stable/topics/db/optimization/",
    "https://docs.djangoproject.com/en/stable/ref/models/querysets/",
    "https://use-the-index-luke.com/sql/clustering/index-only-scan-covering-index",
    "https://docs.sqlalchemy.org/en/20/orm/queryguide/columns.html",
    "https://learn.microsoft.com/en-us/azure/architecture/antipatterns/busy-database/"
   ],
   "orgs": [
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/architecture/antipatterns/extraneous-fetching/",
      "https://learn.microsoft.com/en-us/azure/architecture/antipatterns/busy-database/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "django",
     "name": "Django Software Foundation",
     "sources": [
      "https://docs.djangoproject.com/en/stable/topics/db/optimization/",
      "https://docs.djangoproject.com/en/stable/ref/models/querysets/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "sqlalchemy",
     "name": "SQLAlchemy",
     "sources": [
      "https://docs.sqlalchemy.org/en/20/orm/queryguide/columns.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "winand",
     "name": "Markus Winand",
     "sources": [
      "https://use-the-index-luke.com/sql/clustering/index-only-scan-covering-index"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-fetch-only-needed-data",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-focus-indicator-visible",
   "level": "function",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "accessibility",
    "usability"
   ],
   "topics": [
    "frontend",
    "css",
    "keyboard"
   ],
   "lang": "core",
   "applies_when": "Styles or script change `outline`, `:focus`, or `:focus-visible`.",
   "context": [
    "ui"
   ],
   "tool_gate": "Stylelint `declaration-property-value-disallowed-list` with `outline: [\"none\", \"0\"]`.",
   "note": "SC 2.4.13 (AAA, size and contrast of the indicator) is not required here; the 3:1 figure comes from web.dev and SC 1.4.11. Focus hidden under a sticky bar (SC 2.4.11) has 2 orgs (W3C, Google), so it is in Why, not the Rule",
   "lacks": null,
   "rule": "Keep a visible focus indicator on every focusable element. Never set `outline: none` without a replacement.",
   "why": "A keyboard user finds their place by the focus ring. Remove it and they navigate blind. A sticky header that covers the focused link has the same effect once they tab down the page. `scroll-padding-top` on `html`, as tall as the header, keeps the focused element in view.",
   "counterpoint": "\"Designers dislike the ring on mouse clicks.\" `:focus-visible` shows the ring for keyboard focus and hides it for most mouse clicks. Restyle the ring if you must. Do not remove it.",
   "fail": {
    "language": "css",
    "code": "*:focus { outline: none; }\na:focus { outline: 0; }"
   },
   "pass": {
    "language": "css",
    "code": ":focus-visible { outline: 3px solid #1a56db; outline-offset: 2px; }"
   },
   "sources": [
    "https://www.w3.org/WAI/WCAG22/Understanding/focus-visible.html",
    "https://www.w3.org/WAI/WCAG22/Understanding/focus-not-obscured-minimum.html",
    "https://web.dev/learn/accessibility/focus",
    "https://webaim.org/techniques/keyboard/",
    "https://www.a11yproject.com/checklist/"
   ],
   "orgs": [
    {
     "id": "w3c",
     "name": "W3C",
     "sources": [
      "https://www.w3.org/WAI/WCAG22/Understanding/focus-visible.html",
      "https://www.w3.org/WAI/WCAG22/Understanding/focus-not-obscured-minimum.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://web.dev/learn/accessibility/focus"
     ],
     "strength": "unjudged"
    },
    {
     "id": "webaim",
     "name": "WebAIM",
     "sources": [
      "https://webaim.org/techniques/keyboard/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "a11yproject",
     "name": "The A11y Project",
     "sources": [
      "https://www.a11yproject.com/checklist/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-focus-indicator-visible",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-form-control-has-label",
   "level": "function",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "accessibility",
    "usability"
   ],
   "topics": [
    "frontend",
    "forms"
   ],
   "lang": "core",
   "applies_when": "Markup or a component renders an input, a select, or a textarea.",
   "context": [
    "ui"
   ],
   "tool_gate": "ESLint `jsx-a11y/label-has-associated-control` and axe `label`.",
   "note": "Label tie: W3C, MDN, A11y Project, jsx-a11y. A placeholder is not a label: Deque, GOV.UK, WebAIM. USWDS covers only fieldset and legend",
   "lacks": null,
   "rule": "Tie every form control to a visible `<label>`, with `for` and a matching `id` or by wrapping it. A placeholder is not a label.",
   "why": "A screen reader announces a bare field as \"edit text\". Voice-control users cannot say its name. A label also enlarges the click target. A placeholder vanishes when the user types, so it fails every user who looks away and comes back.",
   "counterpoint": "\"The design shows a floating placeholder label.\" A floating label is a real label if it stays visible and is wired to the input. Use `aria-label` only where the purpose is already visible, such as a search field next to a Search button.",
   "fail": {
    "language": "html",
    "code": "<input type=\"email\" placeholder=\"Email\">\n<div class=\"label\">Name</div>\n<input type=\"text\" id=\"name\">"
   },
   "pass": {
    "language": "html",
    "code": "<label for=\"email\">Email</label>\n<input type=\"email\" id=\"email\" autocomplete=\"email\">\n\n<label>Name <input type=\"text\" name=\"name\" autocomplete=\"name\"></label>"
   },
   "sources": [
    "https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html",
    "https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Accessibility/HTML",
    "https://dequeuniversity.com/rules/axe/html/4.10/label",
    "https://www.a11yproject.com/checklist/",
    "https://github.com/jsx-eslint/eslint-plugin-jsx-a11y",
    "https://design-system.service.gov.uk/components/text-input/",
    "https://webaim.org/techniques/forms/advanced"
   ],
   "orgs": [
    {
     "id": "w3c",
     "name": "W3C",
     "sources": [
      "https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Accessibility/HTML"
     ],
     "strength": "unjudged"
    },
    {
     "id": "deque",
     "name": "Deque",
     "sources": [
      "https://dequeuniversity.com/rules/axe/html/4.10/label"
     ],
     "strength": "unjudged"
    },
    {
     "id": "a11yproject",
     "name": "The A11y Project",
     "sources": [
      "https://www.a11yproject.com/checklist/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "jsx-eslint",
     "name": "jsx-eslint project",
     "sources": [
      "https://github.com/jsx-eslint/eslint-plugin-jsx-a11y"
     ],
     "strength": "unjudged"
    },
    {
     "id": "govuk",
     "name": "GOV.UK",
     "sources": [
      "https://design-system.service.gov.uk/components/text-input/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "webaim",
     "name": "WebAIM",
     "sources": [
      "https://webaim.org/techniques/forms/advanced"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-form-control-has-label",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-guard-uri-decoding",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "reliability",
    "correctness"
   ],
   "topics": [
    "javascript"
   ],
   "lang": "core",
   "applies_when": "Code calls `decodeURIComponent` or `decodeURI` on a query parameter, a hash, a path segment or other input a user controls.",
   "context": [
    "ui",
    "server"
   ],
   "tool_gate": null,
   "note": "MDN lists the URIError and shows a try/catch; Node's querystring.unescape falls back to a decoder that does not throw",
   "lacks": null,
   "rule": "Wrap `decodeURIComponent` on user input in a `try`/`catch`, or parse with `URLSearchParams`, so that a malformed `%` sequence cannot throw.",
   "why": "`decodeURIComponent(\"%E0%A4%A\")` throws a `URIError`. A link with a stray `%` in its query then breaks the page script for that visitor, and anyone can craft such a link.",
   "counterpoint": "\"Only our own links reach this code.\" The address bar accepts anything, and other sites, chat apps and crawlers produce broken links. A guard costs three lines.",
   "fail": {
    "language": "javascript",
    "code": "const q = decodeURIComponent(location.hash.slice(1));\nrender(q);"
   },
   "pass": {
    "language": "javascript",
    "code": "let q;\ntry {\n  q = decodeURIComponent(location.hash.slice(1));\n} catch {\n  q = \"\";\n}\nrender(q);"
   },
   "sources": [
    "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent",
    "https://nodejs.org/api/querystring.html",
    "https://raw.githubusercontent.com/remix-run/react-router/main/packages/react-router/lib/router/utils.ts"
   ],
   "orgs": [
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nodejs",
     "name": "OpenJS Foundation",
     "sources": [
      "https://nodejs.org/api/querystring.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "remix",
     "name": "Remix",
     "sources": [
      "https://raw.githubusercontent.com/remix-run/react-router/main/packages/react-router/lib/router/utils.ts"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/function#fn-guard-uri-decoding",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-handle-rejected-promises",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "reliability",
    "correctness"
   ],
   "topics": [
    "javascript"
   ],
   "lang": "core",
   "applies_when": "Code starts a promise that can reject, such as `fetch`, `navigator.clipboard.writeText` or an async function, and does not return it.",
   "context": [],
   "tool_gate": "typescript-eslint `no-floating-promises`.",
   "note": "MDN (Mozilla), web.dev (Google), typescript-eslint and Node.js each state the practice or its effect. Node.js ends the process by default on an unhandled rejection",
   "lacks": null,
   "rule": "Handle the rejection of every promise you start: `await` it inside `try`/`catch`, end the chain with `.catch()`, or return it to a caller that does.",
   "why": "A browser API such as `writeText` rejects when the page lacks permission. With no handler the failure surfaces only as an `unhandledrejection` event, the user sees nothing, and the next step runs as if the call worked. In Node.js 15 and later the same rejection ends the process by default.",
   "counterpoint": "\"A global `unhandledrejection` handler already logs everything.\" It logs after the fact and cannot tell the user what to do. Only the call site knows which message and which fallback fit. Keep the global handler as a net, not as the handling.",
   "fail": {
    "language": "javascript",
    "code": "button.addEventListener(\"click\", () => {\n  navigator.clipboard.writeText(url);\n  showToast(\"Copied\");\n});"
   },
   "pass": {
    "language": "javascript",
    "code": "button.addEventListener(\"click\", async () => {\n  try {\n    await navigator.clipboard.writeText(url);\n    showToast(\"Copied\");\n  } catch (err) {\n    showToast(\"Could not copy. Select the text and copy it by hand.\");\n  }\n});"
   },
   "sources": [
    "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises",
    "https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText",
    "https://web.dev/articles/async-clipboard",
    "https://typescript-eslint.io/rules/no-floating-promises/",
    "https://nodejs.org/api/cli.html"
   ],
   "orgs": [
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises",
      "https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://web.dev/articles/async-clipboard"
     ],
     "strength": "unjudged"
    },
    {
     "id": "typescript-eslint",
     "name": "typescript-eslint project",
     "sources": [
      "https://typescript-eslint.io/rules/no-floating-promises/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nodejs",
     "name": "OpenJS Foundation",
     "sources": [
      "https://nodejs.org/api/cli.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-handle-rejected-promises",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-header-value-encoding",
   "level": "function",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "http",
    "injection"
   ],
   "lang": "core",
   "applies_when": "A handler puts a request-derived value into a response header, such as `Location` or `Set-Cookie`.",
   "context": [
    "server"
   ],
   "tool_gate": "A test that sends `%0d%0a` in the echoed parameter and asserts a 400 or an encoded value; Node and Django throw at runtime, which is a guard rather than a linter.",
   "note": "fn-redirect-target-allowlist checks where a redirect points; this card checks how the value is written. Both clauses have IETF (RFC 9112 names header-writing APIs that filter bad octets), MITRE, OpenJS and Django",
   "lacks": null,
   "rule": "Set every response header through the framework's header API, never by writing to the connection, and reject or encode CR and LF in any request-derived value.",
   "why": "A header value is one line. Bytes that end the line start a new header, so a value the attacker controls can add a header or a second response. The browser follows the attacker's redirect, stores the attacker's cookie, or reads the attacker's body under your origin. Filtering `%0d%0a` at one decode step is not enough, because a second decode or a charset change puts the bytes back.",
   "counterpoint": "\"The framework rejects newlines, so this cannot happen.\" It does reject them, for headers set through its API. The failure sits in the code that writes the status line or the whole response itself, such as a proxy, a websocket handshake, or a hand-rolled error page. Keep those on the API path too.",
   "fail": {
    "language": "python",
    "code": "u = request.query[\"u\"]                    # %0d%0a arrives as CR LF\nconn.write(f\"HTTP/1.1 302 Found\\r\\nLocation: /next?u={u}\\r\\n\\r\\n\")"
   },
   "pass": {
    "language": "python",
    "code": "u = request.query[\"u\"]\nresp = Response(status=302)\nresp.headers[\"Location\"] = \"/next?u=\" + quote(u, safe=\"\")   # quote() encodes CR and LF; the header API rejects any that remain"
   },
   "sources": [
    "https://www.rfc-editor.org/rfc/rfc9112.html",
    "https://cwe.mitre.org/data/definitions/113.html",
    "https://nodejs.org/api/http.html",
    "https://docs.djangoproject.com/en/5.2/ref/request-response/"
   ],
   "orgs": [
    {
     "id": "ietf",
     "name": "IETF",
     "sources": [
      "https://www.rfc-editor.org/rfc/rfc9112.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mitre",
     "name": "MITRE",
     "sources": [
      "https://cwe.mitre.org/data/definitions/113.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nodejs",
     "name": "OpenJS Foundation",
     "sources": [
      "https://nodejs.org/api/http.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "django",
     "name": "Django Software Foundation",
     "sources": [
      "https://docs.djangoproject.com/en/5.2/ref/request-response/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-header-value-encoding",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-heading-structure",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "accessibility",
    "usability"
   ],
   "topics": [
    "frontend",
    "html"
   ],
   "lang": "core",
   "applies_when": "Markup or a component renders a title or a section heading.",
   "context": [
    "ui"
   ],
   "tool_gate": "axe `p-as-heading` (experimental) flags a bold paragraph styled as a heading; axe `heading-order` flags skipped levels.",
   "note": "3 orgs (W3C, Mozilla, A11y Project) for real heading elements. No skipped levels has 2 (W3C, A11y Project), so it is in Why. The one-`h1`-per-page rule is the A11y Project's alone, so this card does not require it",
   "lacks": null,
   "rule": "Mark every heading with a real `<h1>` to `<h6>` element, not a styled `div`, `span`, or `p`.",
   "why": "Screen-reader users jump from heading to heading to scan a page. A `div` styled as a heading is invisible to that. Pick the level from the outline, not from the size you want: a skipped level, such as `h2` to `h4`, makes the outline look as if a section is missing.",
   "counterpoint": "\"The design needs a small `h2`.\" That is a CSS problem. Keep the level that matches the outline and set the size in the stylesheet. Jumping back up a level, such as `h4` to `h2`, is fine when a section ends.",
   "fail": {
    "language": "html",
    "code": "<div class=\"title-large\">Account</div>\n<p><b>Billing</b></p>\n<span class=\"bold\">Invoices</span>"
   },
   "pass": {
    "language": "html",
    "code": "<h1>Account</h1>\n<h2>Billing</h2>\n<h3>Invoices</h3>\n<!-- size comes from CSS: h2 { font-size: 1rem } -->"
   },
   "sources": [
    "https://www.w3.org/WAI/tutorials/page-structure/headings/",
    "https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html",
    "https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Accessibility/HTML",
    "https://www.a11yproject.com/checklist/"
   ],
   "orgs": [
    {
     "id": "w3c",
     "name": "W3C",
     "sources": [
      "https://www.w3.org/WAI/tutorials/page-structure/headings/",
      "https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Accessibility/HTML"
     ],
     "strength": "unjudged"
    },
    {
     "id": "a11yproject",
     "name": "The A11y Project",
     "sources": [
      "https://www.a11yproject.com/checklist/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-heading-structure",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-img-alt-text",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "accessibility"
   ],
   "topics": [
    "frontend",
    "html"
   ],
   "lang": "core",
   "applies_when": "Markup or a component renders an image, an icon, or a chart.",
   "context": [
    "ui"
   ],
   "tool_gate": "ESLint `jsx-a11y/alt-text` and axe `image-alt` catch a missing `alt`. Only a person can judge whether the text is accurate.",
   "note": "5 orgs back the rule; no tool can check that the text is right",
   "lacks": null,
   "rule": "Give every `<img>` an `alt` attribute: text that serves the image's purpose, or `alt=\"\"` when the image is decorative.",
   "why": "A screen reader has nothing to say about an image with no `alt`, and often reads the file name instead. An informative image with an empty `alt` hides its content. A decorative image with a long `alt` adds noise to every page view.",
   "counterpoint": "\"A linter already checks `alt`.\" It checks that the attribute exists, not that the text fits the image or the context. Review the text itself. For a chart, give a short `alt` plus the data nearby as text.",
   "fail": {
    "language": "html",
    "code": "<img src=\"/img/IMG_0042.jpg\">\n<img src=\"/img/chart.png\" alt=\"chart\">\n<img src=\"/img/divider.png\" alt=\"divider line\">\n<button><img src=\"/img/trash.svg\"></button>"
   },
   "pass": {
    "language": "html",
    "code": "<img src=\"/img/q3-revenue.png\" alt=\"Revenue grew from 4.1 to 5.3 million in Q3\">\n<img src=\"/img/divider.png\" alt=\"\">\n<button><img src=\"/img/trash.svg\" alt=\"Delete draft\"></button>"
   },
   "sources": [
    "https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html",
    "https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Accessibility/HTML",
    "https://dequeuniversity.com/rules/axe/html/4.10/image-alt",
    "https://www.a11yproject.com/checklist/",
    "https://github.com/jsx-eslint/eslint-plugin-jsx-a11y"
   ],
   "orgs": [
    {
     "id": "w3c",
     "name": "W3C",
     "sources": [
      "https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Accessibility/HTML"
     ],
     "strength": "unjudged"
    },
    {
     "id": "deque",
     "name": "Deque",
     "sources": [
      "https://dequeuniversity.com/rules/axe/html/4.10/image-alt"
     ],
     "strength": "unjudged"
    },
    {
     "id": "a11yproject",
     "name": "The A11y Project",
     "sources": [
      "https://www.a11yproject.com/checklist/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "jsx-eslint",
     "name": "jsx-eslint project",
     "sources": [
      "https://github.com/jsx-eslint/eslint-plugin-jsx-a11y"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-img-alt-text",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-independent-work-concurrent",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "performance",
    "correctness"
   ],
   "topics": [
    "concurrency"
   ],
   "lang": "core",
   "applies_when": "A function awaits or calls several I/O operations that do not depend on each other.",
   "context": [],
   "tool_gate": null,
   "note": "Start independent work together: Python, Mozilla, Go (errgroup). Sequence only when one call needs another's result: Mozilla, Microsoft (a vendor), Apple (a vendor); the cursor/plugins skill is opinion and supports it",
   "lacks": null,
   "rule": "Start independent I/O together and wait for all of it; run calls in sequence only when one needs the result of another.",
   "why": "Sequential awaits add their latencies. Three 200 ms calls take 600 ms in a row and 200 ms together.",
   "counterpoint": "\"Parallel calls load the dependency harder and complicate errors.\" Cap fan-out with a semaphore or pool, and use a task group so one failure cancels the rest. A deliberate order, for rate limits or ordering, is a valid reason. Write it down.",
   "fail": {
    "language": "python",
    "code": "user = await get_user(uid)\norders = await get_orders(uid)       # does not use `user`\nprefs = await get_prefs(uid)"
   },
   "pass": {
    "language": "python",
    "code": "user, orders, prefs = await asyncio.gather(\n    get_user(uid), get_orders(uid), get_prefs(uid))"
   },
   "sources": [
    "https://pkg.go.dev/golang.org/x/sync/errgroup",
    "https://github.com/cursor/plugins/blob/main/thermos/skills/thermo-nuclear-code-quality-review/SKILL.md",
    "https://docs.python.org/3/library/asyncio-task.html",
    "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all",
    "https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/",
    "https://developer.apple.com/videos/play/wwdc2025/268/"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://pkg.go.dev/golang.org/x/sync/errgroup"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cursor",
     "name": "Cursor",
     "sources": [
      "https://github.com/cursor/plugins/blob/main/thermos/skills/thermo-nuclear-code-quality-review/SKILL.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://docs.python.org/3/library/asyncio-task.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "apple",
     "name": "Apple",
     "sources": [
      "https://developer.apple.com/videos/play/wwdc2025/268/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-independent-work-concurrent",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-join-dont-concat-in-loop",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "performance"
   ],
   "topics": [
    "strings"
   ],
   "lang": "core",
   "applies_when": "A function builds a string from pieces in a loop whose length depends on input.",
   "context": [],
   "tool_gate": "Linter rule for string concatenation in a loop (for example pylint `consider-using-join`).",
   "note": null,
   "lacks": null,
   "rule": "Build a string from a loop with a builder or a join, never with repeated `+=`.",
   "why": "Strings are immutable in Python, Go, Java, and .NET, so each `+=` copies everything built so far. Over n pieces the total cost grows with the square of the output length.",
   "counterpoint": "\"CPython optimises `+=` in place.\" Sometimes: only in CPython, and only while nothing else holds a reference to the string. Code that leans on it turns quadratic on other runtimes. A small fixed number of pieces is fine with `+` or an f-string, which read better. The rule covers loops over input-sized data.",
   "fail": {
    "language": "python",
    "code": "out = \"\"\nfor row in rows:\n    out += f\"{row.id},{row.name}\\n\""
   },
   "pass": {
    "language": "python",
    "code": "out = \"\".join(f\"{row.id},{row.name}\\n\" for row in rows)"
   },
   "sources": [
    "https://docs.python.org/3/faq/programming.html",
    "https://pkg.go.dev/strings#Builder",
    "https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/strings/common-tasks/concatenate"
   ],
   "orgs": [
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://docs.python.org/3/faq/programming.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://pkg.go.dev/strings#Builder"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/strings/common-tasks/concatenate"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-join-dont-concat-in-loop",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-jwt-verify-pinned",
   "level": "function",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "authn",
    "jwt"
   ],
   "lang": "core",
   "applies_when": "Code accepts a JWT as proof of identity or permission.",
   "context": [
    "server"
   ],
   "tool_gate": "CodeQL `js/jwt-missing-verification`; a grep for `verify_signature\": False` and for `jwt.decode(` calls with no `algorithms=`.",
   "note": null,
   "lacks": null,
   "rule": "Verify the signature against a fixed list of accepted algorithms, and check `iss` and `aud`, before reading any claim.",
   "why": "If the verifier takes the algorithm from the token header, an attacker picks `none` or turns an RSA public key into an HMAC secret. Claims read before verification are attacker text. Pinning the algorithm and binding each key to one algorithm closes both. Also require `exp`, so a stolen token stops working.",
   "counterpoint": "\"Our gateway already verifies the token.\" Then the service trusts a header that any internal caller can forge. Verify at each trust boundary, or prove that only the gateway can reach the service.",
   "fail": {
    "language": "python",
    "code": "claims = jwt.decode(token, options={\"verify_signature\": False})\nclaims = jwt.decode(token, key)               # PyJWT 1.x: algorithm taken from the header"
   },
   "pass": {
    "language": "python",
    "code": "claims = jwt.decode(token, key, algorithms=[\"RS256\"],\n                    issuer=ISSUER, audience=AUD,\n                    options={\"require\": [\"exp\", \"iss\", \"aud\"]})"
   },
   "sources": [
    "https://www.rfc-editor.org/rfc/rfc8725",
    "https://top10.owasp.org/2025/A01_2025-Broken_Access_Control",
    "https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_Cheat_Sheet.html",
    "https://learn.microsoft.com/en-us/entra/identity-platform/access-tokens",
    "https://developers.google.com/identity/gsi/web/guides/verify-google-id-token",
    "https://codeql.github.com/codeql-query-help/javascript/js-jwt-missing-verification/",
    "https://pyjwt.readthedocs.io/en/stable/api.html"
   ],
   "orgs": [
    {
     "id": "ietf",
     "name": "IETF",
     "sources": [
      "https://www.rfc-editor.org/rfc/rfc8725"
     ],
     "strength": "unjudged"
    },
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://top10.owasp.org/2025/A01_2025-Broken_Access_Control",
      "https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/entra/identity-platform/access-tokens"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://developers.google.com/identity/gsi/web/guides/verify-google-id-token"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://codeql.github.com/codeql-query-help/javascript/js-jwt-missing-verification/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "pyjwt",
     "name": "PyJWT project",
     "sources": [
      "https://pyjwt.readthedocs.io/en/stable/api.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-jwt-verify-pinned",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-keep-input-on-error",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "usability"
   ],
   "topics": [
    "frontend",
    "forms"
   ],
   "lang": "core",
   "applies_when": "A form handler or component re-renders after a failed validation or a failed submit.",
   "context": [
    "ui"
   ],
   "tool_gate": "none; a test that submits an invalid form and asserts the entered values are still there.",
   "note": "GOV.UK says validate on submit and Baymard says validate when the user leaves a field; this card takes no side on timing. Shneiderman's rule 8 is about re-entry across screens and is related, not the same.",
   "lacks": null,
   "rule": "After a failed validation or submit, show the form again with every value the user entered, correct or not.",
   "why": "A form that wipes its fields turns one typo into ten minutes of retyping, and some users give up. The user fixes one field, not the whole page.",
   "counterpoint": "\"Clear a password or card number on error for safety.\" That is a fair exception for secrets, and the message should say so. Everything else the user typed stays.",
   "fail": {
    "language": "jsx",
    "code": "async function submit(e) {\n  e.preventDefault();\n  const res = await post(\"/signup\", values);\n  if (!res.ok) { setErrors(res.errors); setValues(EMPTY); }\n}"
   },
   "pass": {
    "language": "jsx",
    "code": "async function submit(e) {\n  e.preventDefault();\n  const res = await post(\"/signup\", values);\n  if (!res.ok) setErrors(res.errors);   // values stay as typed\n}"
   },
   "sources": [
    "https://www.nngroup.com/articles/error-message-guidelines/",
    "https://design-system.service.gov.uk/components/error-message/",
    "https://design-system.service.gov.uk/patterns/validation/",
    "https://baymard.com/blog/inline-form-validation",
    "https://www.cs.umd.edu/users/ben/goldenrules.html"
   ],
   "orgs": [
    {
     "id": "nngroup",
     "name": "Nielsen Norman Group",
     "sources": [
      "https://www.nngroup.com/articles/error-message-guidelines/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "govuk",
     "name": "GOV.UK",
     "sources": [
      "https://design-system.service.gov.uk/components/error-message/",
      "https://design-system.service.gov.uk/patterns/validation/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "baymard",
     "name": "Baymard Institute",
     "sources": [
      "https://baymard.com/blog/inline-form-validation"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "shneiderman",
     "name": "Ben Shneiderman",
     "sources": [
      "https://www.cs.umd.edu/users/ben/goldenrules.html"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-keep-input-on-error",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-label-above-field",
   "level": "function",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "usability",
    "accessibility"
   ],
   "topics": [
    "frontend",
    "forms",
    "css"
   ],
   "lang": "core",
   "applies_when": "Markup or styles lay out a form field and its visible label.",
   "context": [
    "ui"
   ],
   "tool_gate": null,
   "note": "4 orgs put the label above a text field (GOV.UK, Atlassian, NN/g for mobile and short forms, W3C as one of two conventions). The checkbox and radio order is W3C's alone, so it sits in Why. W3C also says the choice depends on nearby fields, so the severity is discuss. Nothing between label and field has only Atlassian and NN/g, so it sits in Why",
   "lacks": null,
   "rule": "Put the visible label of a text input, select, or textarea directly above it.",
   "why": "A label sitting below a field, or halfway between two fields, gets read as the label of the wrong one. W3C notes that a label above the field cuts sideways scrolling for low-vision and phone users, who can lose a side label off the edge of the screen. Hint and error text can go between the label and the field. A help link, a button, or another field cannot. Checkboxes and radios are the exception: W3C puts the control first and its label to the right, in left-to-right languages.",
   "counterpoint": "\"Labels to the left fit our long desktop admin form better.\" Labels beside the field are an accepted convention for very long desktop forms. Accept a left label on such a form if it stays next to its own field at every breakpoint. A wrapping label, as in the `fn-form-control-has-label` PASS, passes when CSS sets the label text to `display: block` so it sits above the field. A label below the field is still a `FAIL`. This card covers placement only. `fn-form-control-has-label` covers the code tie between label and field.",
   "fail": {
    "language": "html",
    "code": "<input type=\"text\" id=\"town\">\n<label for=\"town\">Town or city</label>"
   },
   "pass": {
    "language": "html",
    "code": "<label for=\"town\">Town or city</label>\n<input type=\"text\" id=\"town\" autocomplete=\"address-level2\">\n\n<label for=\"postcode\">Postcode</label>\n<p id=\"postcode-hint\">For example, SW1A 2AA</p>\n<input type=\"text\" id=\"postcode\" aria-describedby=\"postcode-hint\">"
   },
   "sources": [
    "https://design-system.service.gov.uk/components/text-input/",
    "https://atlassian.design/components/textfield/usage",
    "https://www.nngroup.com/articles/web-form-design/",
    "https://www.w3.org/WAI/tutorials/forms/labels/"
   ],
   "orgs": [
    {
     "id": "govuk",
     "name": "GOV.UK",
     "sources": [
      "https://design-system.service.gov.uk/components/text-input/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "atlassian",
     "name": "Atlassian",
     "sources": [
      "https://atlassian.design/components/textfield/usage"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nngroup",
     "name": "Nielsen Norman Group",
     "sources": [
      "https://www.nngroup.com/articles/web-form-design/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "w3c",
     "name": "W3C",
     "sources": [
      "https://www.w3.org/WAI/tutorials/forms/labels/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-label-above-field",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-length-budget",
   "level": "function",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "readability",
    "size"
   ],
   "lang": "core",
   "applies_when": "A function has grown past the length limit the project sets, or the project has no limit yet.",
   "context": [],
   "tool_gate": "ESLint max-lines-per-function (default 50), Pylint too-many-statements (R0915, max-statements default 50), Checkstyle MethodLength (default 150), RuboCop Metrics/MethodLength (default 10), PMD NcssCount (methodReportLevel default 60). PMD CognitiveComplexity (default 15) and Microsoft CA1505 (maintainability index threshold 10) measure complexity, not length.",
   "note": "length is a signal, not a defect. ESLint, Checkstyle, Pylint, RuboCop and PMD ship a size gate. Google (about 40 lines, no hard limit), the Linux kernel, Pylint and Microsoft CA1505 back the split clause and keep a long simple body, so the card asks for an inspection when a function passes the limit",
   "lacks": null,
   "rule": "Split a function that passes the project's length limit (about 40 lines if it sets none) unless its body is one simple operation or one flat dispatch.",
   "why": "A long body hides its steps. Validation, pricing, a charge, the order write and the receipt become one block, and a reader cannot test, skip or reorder one of them. Linters ship a limit because a long method is hard to understand and does too many things, so set one in the project's linter. The number is where the inspection starts, not a verdict. A body that is one flat dispatch can stay long, because its reader follows one dispatch rule.",
   "counterpoint": "\"A long function is one operation, and the split scatters the sequence.\" Length alone does not decide. A hard limit can force bad splits, which is why Google sets none, so treat the limit as a prompt to inspect. Keep the function when its body is one simple operation or one flat dispatch table. Split when the lines serve different jobs, and let each new function own one of them.",
   "fail": {
    "language": "javascript",
    "code": "// eslint max-lines-per-function: checkout has 140 lines (max 50)\nfunction checkout(cart, user) {\n  // 30 lines validating the cart\n  // 25 lines pricing it\n  // 35 lines charging the card\n  // 25 lines writing the order\n  // 25 lines emailing the receipt\n}"
   },
   "pass": {
    "language": "javascript",
    "code": "function checkout(cart, user) {\n  const total = price(validateCart(cart), user);\n  charge(user, total);\n  saveOrder(user, cart);\n  emailReceipt(user);\n}\n// each helper is under the project's limit"
   },
   "sources": [
    "https://eslint.org/docs/latest/rules/max-lines-per-function",
    "https://pylint.readthedocs.io/en/stable/user_guide/configuration/all-options.html",
    "https://pylint.readthedocs.io/en/stable/user_guide/messages/refactor/too-many-statements.html",
    "https://checkstyle.sourceforge.io/checks/sizes/methodlength.html",
    "https://docs.rubocop.org/rubocop/latest/cops_metrics.html",
    "https://pmd.github.io/pmd/pmd_rules_java_design.html",
    "https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1505",
    "https://google.github.io/styleguide/pyguide.html#318-function-length",
    "https://www.kernel.org/doc/html/latest/process/coding-style.html"
   ],
   "orgs": [
    {
     "id": "eslint",
     "name": "ESLint",
     "sources": [
      "https://eslint.org/docs/latest/rules/max-lines-per-function"
     ],
     "strength": "unjudged"
    },
    {
     "id": "pylint",
     "name": "Pylint",
     "sources": [
      "https://pylint.readthedocs.io/en/stable/user_guide/configuration/all-options.html",
      "https://pylint.readthedocs.io/en/stable/user_guide/messages/refactor/too-many-statements.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "checkstyle",
     "name": "Checkstyle",
     "sources": [
      "https://checkstyle.sourceforge.io/checks/sizes/methodlength.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "rubocop",
     "name": "RuboCop",
     "sources": [
      "https://docs.rubocop.org/rubocop/latest/cops_metrics.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "pmd",
     "name": "PMD",
     "sources": [
      "https://pmd.github.io/pmd/pmd_rules_java_design.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1505"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://google.github.io/styleguide/pyguide.html#318-function-length"
     ],
     "strength": "unjudged"
    },
    {
     "id": "kernel",
     "name": "Linux kernel",
     "sources": [
      "https://www.kernel.org/doc/html/latest/process/coding-style.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-length-budget",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-link-url-scheme-allowlist",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "security"
   ],
   "topics": [
    "xss",
    "javascript"
   ],
   "lang": "core",
   "applies_when": "A URL taken from remote JSON, a query parameter or stored data is written to `href`, `src`, `location` or `window.open`.",
   "context": [
    "ui"
   ],
   "tool_gate": null,
   "note": "OWASP says URL properties need URL validation even where React blocks javascript: URLs; Angular sanitizes URL bindings. fn-no-raw-html-sink covers HTML sinks, and fn-redirect-target-allowlist covers server redirects",
   "lacks": null,
   "rule": "Parse a URL that comes from data and allow only `https:` (and `http:` or `mailto:` where needed) before you set it as a link target.",
   "why": "A `javascript:` URL in an `href` runs script in your origin when clicked, and a data feed or an API you do not fully control can supply one. Some frameworks block it, but the check belongs to the app, because the next sink may not be covered.",
   "counterpoint": "\"The feed is ours, so it is trusted.\" It is trusted until a third party writes into it or someone breaches it. The check is two lines and does not depend on who owns the source.",
   "fail": {
    "language": "javascript",
    "code": "const item = await (await fetch(\"/feed.json\")).json();\nlink.href = item.url;"
   },
   "pass": {
    "language": "javascript",
    "code": "const item = await (await fetch(\"/feed.json\")).json();\nconst u = new URL(item.url, location.origin);\nlink.href = [\"https:\", \"http:\"].includes(u.protocol) ? u.href : \"#\";"
   },
   "sources": [
    "https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html",
    "https://angular.dev/best-practices/security",
    "https://developer.wordpress.org/reference/functions/esc_url/"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://angular.dev/best-practices/security"
     ],
     "strength": "unjudged"
    },
    {
     "id": "wordpress",
     "name": "WordPress",
     "sources": [
      "https://developer.wordpress.org/reference/functions/esc_url/"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/function#fn-link-url-scheme-allowlist",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-mask-credentials-in-output",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "security",
    "usability"
   ],
   "topics": [
    "secrets"
   ],
   "lang": "core",
   "applies_when": "A CLI command, admin page or debug view prints stored configuration that can include tokens, API keys, or URLs with embedded secrets.",
   "context": [
    "cli"
   ],
   "tool_gate": "A test that seeds a fake token, runs the list/show/debug command, and asserts the full value is absent from stdout and stderr.",
   "note": "sys-secrets-and-errors covers secrets in errors and logs; this card covers a command whose purpose is to display configuration. svc-no-secrets-in-url covers secrets in request URLs",
   "lacks": null,
   "rule": "Mask credentials in list, show and debug output (keep only the last four characters), and print a full value only behind an explicit flag.",
   "why": "Output gets pasted into tickets, chat and CI logs, and screen-shared. A webhook URL that carries its token, or an API key shown whole by `config show`, becomes a leak the first time someone asks for help. Masking keeps the output useful for telling two credentials apart.",
   "counterpoint": "\"Users need the value to copy it.\" Then give them an explicit reveal flag that is off by default, so the copy is a deliberate act and the default output is safe to paste. Never ask users to scrape the full secret out of a listing.",
   "fail": {
    "language": "python",
    "code": "for hook in hooks:\n    print(f\"{hook.name}\\t{hook.url}\")      # https://hooks.example.com/T0/B1/xK9...full secret\nprint(\"api_key:\", settings.api_key)"
   },
   "pass": {
    "language": "python",
    "code": "def mask(value: str) -> str:\n    return \"****\" + value[-4:] if len(value) > 8 else \"****\"\n\nfor hook in hooks:\n    print(f\"{hook.name}\\t{urlsplit(hook.url).netloc}/...{hook.url[-4:]}\")\nprint(\"api_key:\", mask(settings.api_key))\n# `show --reveal` prints the full value on request"
   },
   "sources": [
    "https://cli.github.com/manual/gh_auth_status",
    "https://docs.djangoproject.com/en/5.2/ref/settings/",
    "https://docs.gitlab.com/ci/variables/",
    "https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html",
    "https://docs.aws.amazon.com/cli/latest/reference/configure/list.html",
    "https://kubernetes.io/docs/reference/kubectl/generated/kubectl_config/kubectl_config_view/",
    "https://www.pulumi.com/docs/iac/concepts/secrets/"
   ],
   "orgs": [
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://cli.github.com/manual/gh_auth_status"
     ],
     "strength": "unjudged"
    },
    {
     "id": "django",
     "name": "Django Software Foundation",
     "sources": [
      "https://docs.djangoproject.com/en/5.2/ref/settings/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://docs.gitlab.com/ci/variables/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://docs.aws.amazon.com/cli/latest/reference/configure/list.html"
     ],
     "strength": "supports"
    },
    {
     "id": "kubernetes",
     "name": "Kubernetes",
     "sources": [
      "https://kubernetes.io/docs/reference/kubectl/generated/kubectl_config/kubectl_config_view/"
     ],
     "strength": "supports"
    },
    {
     "id": "pulumi",
     "name": "Pulumi",
     "sources": [
      "https://www.pulumi.com/docs/iac/concepts/secrets/"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "strong",
   "url": "https://goodcodereview.com/function#fn-mask-credentials-in-output",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-name-intermediate-conditions",
   "level": "function",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "readability"
   ],
   "lang": "core",
   "applies_when": "An `if` or `while` joins three or more terms in one expression.",
   "context": [],
   "tool_gate": null,
   "note": null,
   "lacks": null,
   "rule": "Name each compound condition by assigning its sub-conditions to booleans that say what they mean.",
   "why": "Dense conditions force the reader to simulate them. A name states the intent and gives a place to put the next clause.",
   "counterpoint": "\"More variables, more state to track.\" These are immutable one-use names, and they replace a comment you would otherwise write. If the names turn out vague (`flag1`), the condition probably mixes two concepts. Fix that instead.",
   "fail": {
    "language": "text",
    "code": "if u.active and not u.banned and (u.plan == \"pro\" or u.trial_days > 0) and not u.locked:"
   },
   "pass": {
    "language": "text",
    "code": "can_login = u.active and not u.banned and not u.locked\nhas_access = u.plan == Plan.PRO or u.trial_days > 0\nif can_login and has_access:"
   },
   "sources": [
    "https://dev.to/sportebois/software-design-red-flags-wisdom-nuggets-from-john-ousterhout-43i2",
    "https://www.industriallogic.com/papers/extractboolean.html",
    "https://developer.gnome.org/documentation/guidelines/programming/coding-style.html",
    "https://docs.unrealengine.com/5.0/en-US/epic-cplusplus-coding-standard-for-unreal-engine/"
   ],
   "orgs": [
    {
     "id": "industriallogic",
     "name": "Industrial Logic",
     "sources": [
      "https://www.industriallogic.com/papers/extractboolean.html"
     ],
     "strength": "supports"
    },
    {
     "id": "gnome",
     "name": "GNOME",
     "sources": [
      "https://developer.gnome.org/documentation/guidelines/programming/coding-style.html"
     ],
     "strength": "supports"
    },
    {
     "id": "epicgames",
     "name": "Epic Games",
     "sources": [
      "https://docs.unrealengine.com/5.0/en-US/epic-cplusplus-coding-standard-for-unreal-engine/"
     ],
     "strength": "supports"
    }
   ],
   "people": [
    {
     "id": "portebois",
     "name": "Sébastien Portebois",
     "sources": [
      "https://dev.to/sportebois/software-design-red-flags-wisdom-nuggets-from-john-ousterhout-43i2"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "strong",
   "url": "https://goodcodereview.com/function#fn-name-intermediate-conditions",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-native-button-or-link",
   "level": "function",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "accessibility",
    "usability"
   ],
   "topics": [
    "frontend",
    "html"
   ],
   "lang": "core",
   "applies_when": "A component handles a click or tap, or navigates.",
   "context": [
    "ui"
   ],
   "tool_gate": "ESLint `jsx-a11y/click-events-have-key-events` and `jsx-a11y/no-static-element-interactions`; `jsx-a11y/anchor-is-valid` for `href=\"#\"`.",
   "note": "4 orgs; Norman's perceived-affordance essay is context only",
   "lacks": null,
   "rule": "Use `<button>` for an action and `<a href>` for navigation. Do not attach a click handler to a `div`, `span`, or `a` with no `href`.",
   "why": "A `div` is not in the tab order, has no role, and ignores Enter and Space. A keyboard or screen-reader user cannot reach or operate it. A fake link also loses open-in-new-tab and copy-link. A native element gives all of that with no code.",
   "counterpoint": "\"Native buttons are hard to style.\" They are not: reset `border`, `background`, and `font` on the element. Rebuilding the keyboard, role, and focus behaviour by hand costs more and fails more often. Where a custom widget is unavoidable, follow the WAI-ARIA Authoring Practices pattern for it.",
   "fail": {
    "language": "jsx",
    "code": "<div className=\"btn\" onClick={save}>Save</div>\n<a href=\"#\" onClick={openMenu}>Menu</a>\n<span onClick={() => navigate(\"/billing\")}>Billing</span>"
   },
   "pass": {
    "language": "jsx",
    "code": "<button type=\"button\" onClick={save}>Save</button>\n<button type=\"button\" onClick={openMenu}>Menu</button>\n<a href=\"/billing\">Billing</a>"
   },
   "sources": [
    "https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html",
    "https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Accessibility/HTML",
    "https://webaim.org/techniques/keyboard/",
    "https://www.w3.org/WAI/ARIA/apg/practices/read-me-first/",
    "https://github.com/jsx-eslint/eslint-plugin-jsx-a11y",
    "https://jnd.org/affordances-and-design/"
   ],
   "orgs": [
    {
     "id": "w3c",
     "name": "W3C",
     "sources": [
      "https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html",
      "https://www.w3.org/WAI/ARIA/apg/practices/read-me-first/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Accessibility/HTML"
     ],
     "strength": "unjudged"
    },
    {
     "id": "webaim",
     "name": "WebAIM",
     "sources": [
      "https://webaim.org/techniques/keyboard/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "jsx-eslint",
     "name": "jsx-eslint project",
     "sources": [
      "https://github.com/jsx-eslint/eslint-plugin-jsx-a11y"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "jnd",
     "name": "Don Norman",
     "sources": [
      "https://jnd.org/affordances-and-design/"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-native-button-or-link",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-no-blocking-call-in-async-code",
   "level": "function",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "performance",
    "correctness"
   ],
   "topics": [
    "concurrency",
    "io"
   ],
   "lang": "core",
   "applies_when": "Code runs on an event loop or an async runtime (asyncio, Node.js, Tokio, ASP.NET async handlers).",
   "context": [],
   "tool_gate": "Linter rule for blocking calls in async functions (for example ruff ASYNC); asyncio debug mode flags slow callbacks.",
   "note": null,
   "lacks": null,
   "rule": "Never make a blocking I/O call or run long CPU work directly in async code; use the async API or hand the work to a thread or worker.",
   "why": "An event loop runs one callback at a time. A `time.sleep`, a synchronous HTTP call, or a big hash inside a handler stalls every other request on that loop, not just the slow one.",
   "counterpoint": "\"This call is fast enough.\" A short read from a local SSD can be quicker synchronously. Measure it with asyncio debug mode or the runtime's equivalent. A call that crosses the network, or that waits on anything outside the process, is never in that class.",
   "fail": {
    "language": "python",
    "code": "async def handler(req):\n    data = requests.get(url).json()          # blocks the loop\n    digest = hashlib.pbkdf2_hmac(\"sha256\", pw, salt, 600_000)"
   },
   "pass": {
    "language": "python",
    "code": "async def handler(req):\n    data = (await client.get(url)).json()    # httpx.AsyncClient\n    digest = await asyncio.to_thread(hashlib.pbkdf2_hmac, \"sha256\", pw, salt, 600_000)"
   },
   "sources": [
    "https://docs.python.org/3/library/asyncio-dev.html",
    "https://nodejs.org/en/learn/asynchronous-work/dont-block-the-event-loop",
    "https://docs.rs/tokio/latest/tokio/",
    "https://learn.microsoft.com/en-us/azure/architecture/antipatterns/synchronous-io/"
   ],
   "orgs": [
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://docs.python.org/3/library/asyncio-dev.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nodejs",
     "name": "OpenJS Foundation",
     "sources": [
      "https://nodejs.org/en/learn/asynchronous-work/dont-block-the-event-loop"
     ],
     "strength": "unjudged"
    },
    {
     "id": "tokio",
     "name": "Tokio project",
     "sources": [
      "https://docs.rs/tokio/latest/tokio/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/architecture/antipatterns/synchronous-io/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-no-blocking-call-in-async-code",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-no-caller-mutation",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "correctness",
    "testing"
   ],
   "topics": [
    "state"
   ],
   "lang": "core",
   "applies_when": "A function writes to a parameter or to an object the caller still owns, such as a list, map, state object or shared configuration.",
   "context": [],
   "tool_gate": "ESLint no-param-reassign with props true (the default props false misses param.field = value); Pylint dangerous-default-value (W0102) for a mutable default.",
   "note": "parameters and caller-owned objects only; module and class state belongs to mod-no-module-mutable-state, and a fresh local object may be mutated before it is returned",
   "lacks": null,
   "rule": "Do not mutate a parameter or an object the caller owns, unless the function is documented as mutating it.",
   "why": "The caller still holds the old reference, so the change lands at a distance. A list passed to one function changes under the next loop, a shared default collects values across calls, and a mutated state object never triggers a re-render. Return a new value or a copy: the change shows at the call site and the old value stays safe to compare. A fresh local object is different. Nothing else references it yet, so build it in place and return it.",
   "counterpoint": "\"Copying on every update is expensive, and an in-place reducer is simpler.\" Copy only the part you change and share the rest, which keeps the cost low for wide objects. An in-place API is honest when its name says it mutates and its caller owns the object, as with a builder or a buffer. The failure is a quiet change to something the caller still uses. A linter set to catch only reassignment will miss a field write, so turn on the property check too.",
   "fail": {
    "language": "python",
    "code": "def apply_discount(order):\n    order.total *= 0.9      # the caller's order changes too\n    return order"
   },
   "pass": {
    "language": "python",
    "code": "def apply_discount(order):\n    return dataclasses.replace(order, total=order.total * 0.9)\n\ndef normalize(items):\n    out = []                # local mutation is fine\n    for item in items:\n        out.append(item.strip())\n    return out"
   },
   "sources": [
    "https://airbnb.io/javascript/#functions--mutate-params",
    "https://react.dev/learn/updating-objects-in-state",
    "https://docs.python.org/3/faq/programming.html#why-are-default-values-shared-between-objects",
    "https://eslint.org/docs/latest/rules/no-param-reassign",
    "https://google.github.io/styleguide/pyguide.html#212-default-argument-values",
    "https://kotlinlang.org/docs/coding-conventions.html"
   ],
   "orgs": [
    {
     "id": "airbnb",
     "name": "Airbnb",
     "sources": [
      "https://airbnb.io/javascript/#functions--mutate-params"
     ],
     "strength": "unjudged"
    },
    {
     "id": "meta",
     "name": "Meta",
     "sources": [
      "https://react.dev/learn/updating-objects-in-state"
     ],
     "strength": "unjudged"
    },
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://docs.python.org/3/faq/programming.html#why-are-default-values-shared-between-objects"
     ],
     "strength": "unjudged"
    },
    {
     "id": "eslint",
     "name": "ESLint",
     "sources": [
      "https://eslint.org/docs/latest/rules/no-param-reassign"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://google.github.io/styleguide/pyguide.html#212-default-argument-values"
     ],
     "strength": "unjudged"
    },
    {
     "id": "jetbrains",
     "name": "JetBrains",
     "sources": [
      "https://kotlinlang.org/docs/coding-conventions.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-no-caller-mutation",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-no-check-then-act-across-await",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "correctness"
   ],
   "topics": [
    "concurrency"
   ],
   "lang": "core",
   "applies_when": "Code reads shared state, yields (await, I/O, a lock release), then writes based on the earlier read.",
   "context": [],
   "tool_gate": null,
   "note": "from the author's own Python review guide",
   "lacks": null,
   "rule": "Make check-and-write one atomic operation where the state lives, and never split it across a suspension point.",
   "why": "Another task runs at every yield. Two withdrawals both read 100, both pass the check, and the balance goes negative.",
   "counterpoint": "\"A lock fixes it.\" A process-local lock covers one process. With two workers or two hosts, you need a transaction, an atomic statement, an idempotency key, or optimistic concurrency. Name the other readers and writers of that state.",
   "fail": {
    "language": "text",
    "code": "balance = await repo.get_balance(acct)\nif balance >= amount:\n    await repo.set_balance(acct, balance - amount)"
   },
   "pass": {
    "language": "text",
    "code": "# UPDATE accounts SET balance = balance - :amt WHERE id = :id AND balance >= :amt\nif not await repo.debit_if_sufficient(acct, amount):\n    raise InsufficientFunds(acct)"
   },
   "sources": [
    "https://community.owasp.org/pages/vulnerabilities/race_conditions",
    "https://dev.mysql.com/doc/refman/8.4/en/innodb-locking-reads.html",
    "https://www.postgresql.org/docs/current/applevel-consistency.html"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://community.owasp.org/pages/vulnerabilities/race_conditions"
     ],
     "strength": "supports"
    },
    {
     "id": "oracle",
     "name": "Oracle",
     "sources": [
      "https://dev.mysql.com/doc/refman/8.4/en/innodb-locking-reads.html"
     ],
     "strength": "supports"
    },
    {
     "id": "postgresql",
     "name": "PostgreSQL",
     "sources": [
      "https://www.postgresql.org/docs/current/applevel-consistency.html"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "strong",
   "url": "https://goodcodereview.com/function#fn-no-check-then-act-across-await",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-no-eval-untrusted",
   "level": "function",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "injection"
   ],
   "lang": "core",
   "applies_when": "Code runs `eval`, `exec`, or `new Function`, or compiles a template, and some of the text comes from a request, a file, a database row, or model output.",
   "context": [],
   "tool_gate": "Bandit B307 (`eval`) and B102 (`exec_used`); CodeQL `py/code-injection` and `py/template-injection`; ESLint `no-eval` and `no-implied-eval`.",
   "note": "overlaps fn-no-shell-with-input (commands, not code) and fn-no-raw-html-sink (HTML, not templates); ASVS 1.3.2 and 1.3.7",
   "lacks": null,
   "rule": "Never pass untrusted text to `eval`, `exec`, `new Function`, or a template compiler. Pass it to a fixed template or a parser as data.",
   "why": "Each of these turns a string into code that runs with the server's rights, so one field becomes remote code execution. A template engine counts: `render_template_string(request.args[\"msg\"])` lets `{{ config }}` or worse run on the server. An expression language, such as SpEL, carries the same risk. Parse the data format instead (`json.loads`, `ast.literal_eval`), and keep templates in files the author wrote, with user values passed as variables.",
   "counterpoint": "\"Users need formulas, so we need `eval`.\" Write or adopt a small expression parser that knows only numbers and the operators you allow. A sandbox around `eval` loses to the next escape someone publishes.",
   "fail": {
    "language": "python",
    "code": "total = eval(request.args[\"expr\"])\nhtml = render_template_string(\"Hello \" + request.args[\"name\"])"
   },
   "pass": {
    "language": "python",
    "code": "data = json.loads(request.get_data())\nhtml = render_template(\"hello.html\", name=request.args[\"name\"])"
   },
   "sources": [
    "https://raw.githubusercontent.com/OWASP/ASVS/master/5.0/en/0x10-V1-Encoding-and-Sanitization.md",
    "https://cwe.mitre.org/data/definitions/94.html",
    "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval",
    "https://portswigger.net/web-security/server-side-template-injection",
    "https://codeql.github.com/codeql-query-help/python/py-template-injection/"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://raw.githubusercontent.com/OWASP/ASVS/master/5.0/en/0x10-V1-Encoding-and-Sanitization.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mitre",
     "name": "MITRE",
     "sources": [
      "https://cwe.mitre.org/data/definitions/94.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval"
     ],
     "strength": "unjudged"
    },
    {
     "id": "portswigger",
     "name": "PortSwigger",
     "sources": [
      "https://portswigger.net/web-security/server-side-template-injection"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://codeql.github.com/codeql-query-help/python/py-template-injection/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-no-eval-untrusted",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-no-getattr-for-known-names",
   "level": "function",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "types",
    "readability"
   ],
   "lang": "core",
   "applies_when": "Code reads or writes an attribute or field by a dynamic key when the name is known at write time.",
   "context": [],
   "tool_gate": "ruff B009 / B010",
   "note": "from the author's own Python review guide",
   "lacks": null,
   "rule": "Use plain field access, and reserve dynamic access for genuinely dynamic names.",
   "why": "Dynamic access defeats rename tools, type checking, and grep. A rename misses it, and it fails at runtime.",
   "counterpoint": "\"I loop over field names.\" That is fine when the set of names is the data (a mapping from column names, a serializer). If the names are fixed, unroll the loop.",
   "fail": {
    "language": "text",
    "code": "value = getattr(order, \"total\")"
   },
   "pass": {
    "language": "text",
    "code": "value = order.total"
   },
   "sources": [
    "https://github.com/PyCQA/flake8-bugbear#readme",
    "https://github.com/mozilla-ai/any-llm/blob/main/AGENTS.md",
    "https://github.com/AlmaLinux/astra/blob/master/AGENTS.md"
   ],
   "orgs": [
    {
     "id": "pycqa",
     "name": "PyCQA",
     "sources": [
      "https://github.com/PyCQA/flake8-bugbear#readme"
     ],
     "strength": "supports"
    },
    {
     "id": "mozilla-ai",
     "name": "Mozilla.ai",
     "sources": [
      "https://github.com/mozilla-ai/any-llm/blob/main/AGENTS.md"
     ],
     "strength": "supports"
    },
    {
     "id": "almalinux",
     "name": "AlmaLinux",
     "sources": [
      "https://github.com/AlmaLinux/astra/blob/master/AGENTS.md"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "strong",
   "url": "https://goodcodereview.com/function#fn-no-getattr-for-known-names",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-no-magic-values",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "readability",
    "types"
   ],
   "lang": "core",
   "applies_when": "A literal string or number carries meaning the reader must guess (status codes, mode names, limits).",
   "context": [],
   "tool_gate": "none (partial: ruff PLR2004)",
   "note": null,
   "lacks": null,
   "rule": "Give every meaningful literal a name: an enum member or a constant.",
   "why": "Literals drift. `\"canceled\"` in one file and `\"cancelled\"` in another is a silent bug, and nobody can grep for what `3` means.",
   "counterpoint": "\"A constant used once is noise.\" True for self-evident values (`0`, `1`, `\"\"`, an index into a pair). The rule is for literals whose meaning is not visible at the use site, or that appear twice. The failure is two spellings of one concept, not the literal itself.",
   "fail": {
    "language": "text",
    "code": "if resp.status == 404 or retries > 3:\n    mode = \"fast\""
   },
   "pass": {
    "language": "text",
    "code": "if resp.status == HTTPStatus.NOT_FOUND or retries > MAX_RETRIES:\n    mode = Mode.FAST"
   },
   "sources": [
    "https://google.github.io/eng-practices/review/reviewer/looking-for.html",
    "https://eslint.org/docs/latest/rules/no-magic-numbers",
    "https://wiki.sei.cmu.edu/confluence/display/c/DCL06-C.+Use+meaningful+symbolic+constants+to+represent+literal+values"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://google.github.io/eng-practices/review/reviewer/looking-for.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "eslint",
     "name": "ESLint",
     "sources": [
      "https://eslint.org/docs/latest/rules/no-magic-numbers"
     ],
     "strength": "supports"
    },
    {
     "id": "cmu-sei",
     "name": "Carnegie Mellon SEI",
     "sources": [
      "https://wiki.sei.cmu.edu/confluence/display/c/DCL06-C.+Use+meaningful+symbolic+constants+to+represent+literal+values"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/function#fn-no-magic-values",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-no-mass-assignment",
   "level": "function",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "authz",
    "api"
   ],
   "lang": "core",
   "applies_when": "A handler copies request fields into a model, ORM entity or settings object.",
   "context": [
    "server"
   ],
   "tool_gate": "A custom Semgrep rule for `Model(**request.json)`; tests that post extra fields such as `is_admin` and assert they are ignored or rejected.",
   "note": null,
   "lacks": null,
   "rule": "Bind request data to a DTO or an explicit field allowlist, never straight onto the model.",
   "why": "When the model has `is_admin`, `balance` or `owner_id`, a client that adds those keys to the JSON body sets them. A blocklist fails the day someone adds a new sensitive column. The allowlist still needs the object-level check from [[fn-object-level-authz]].",
   "counterpoint": "\"A DTO per endpoint is boilerplate.\" Generate DTOs from the API schema. The boilerplate costs less than one privilege escalation.",
   "fail": {
    "language": "text",
    "code": "user = User(**request.json)          # body {\"name\": \"a\", \"is_admin\": true}\nuser.save()"
   },
   "pass": {
    "language": "text",
    "code": "class UserIn(BaseModel):            # only editable fields exist here\n    name: str\nuser = User(**UserIn(**request.json).model_dump())"
   },
   "sources": [
    "https://cheatsheetseries.owasp.org/cheatsheets/Mass_Assignment_Cheat_Sheet.html",
    "https://api-security.owasp.org/editions/2023/en/0xa3-broken-object-property-level-authorization",
    "https://learn.microsoft.com/en-us/aspnet/core/data/ef-mvc/crud?view=aspnetcore-9.0",
    "https://guides.rubyonrails.org/action_controller_overview.html",
    "https://docs.djangoproject.com/en/5.2/topics/forms/modelforms/"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Mass_Assignment_Cheat_Sheet.html",
      "https://api-security.owasp.org/editions/2023/en/0xa3-broken-object-property-level-authorization"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/aspnet/core/data/ef-mvc/crud?view=aspnetcore-9.0"
     ],
     "strength": "unjudged"
    },
    {
     "id": "rails",
     "name": "Ruby on Rails",
     "sources": [
      "https://guides.rubyonrails.org/action_controller_overview.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "django",
     "name": "Django Software Foundation",
     "sources": [
      "https://docs.djangoproject.com/en/5.2/topics/forms/modelforms/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-no-mass-assignment",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-no-native-deserialise-untrusted",
   "level": "function",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "deserialisation"
   ],
   "lang": "core",
   "applies_when": "Code turns bytes or text from outside the process into objects.",
   "context": [],
   "tool_gate": "Bandit B301 (`pickle`) and B506 (`yaml_load`), CodeQL `py/unsafe-deserialization`; a grep for `ObjectInputStream`, `BinaryFormatter` and `unserialize` elsewhere.",
   "note": null,
   "lacks": null,
   "rule": "Parse untrusted input with a data-only format, never with a native object deserialiser.",
   "why": "`pickle`, `ObjectInputStream`, `BinaryFormatter` and full-power YAML loaders can run attacker-chosen code while they load, so validating after the call is too late. JSON cannot instantiate arbitrary classes. Validate the parsed data against a schema afterwards, as the PASS does.",
   "counterpoint": "\"The data comes from our own queue.\" Anyone who can write to the queue can run code on every consumer, so the queue is a trust boundary. Signing the payload, or a Java serialisation filter that allowlists classes, narrows the risk while you migrate. The call still fails this check until it reads a data-only format.",
   "fail": {
    "language": "python",
    "code": "obj = pickle.loads(request.data)\ncfg = yaml.load(body, Loader=yaml.Loader)"
   },
   "pass": {
    "language": "python",
    "code": "obj = Model.model_validate_json(request.data)\ncfg = yaml.safe_load(body)"
   },
   "sources": [
    "https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html",
    "https://docs.python.org/3/library/pickle.html",
    "https://learn.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide",
    "https://docs.oracle.com/en/java/javase/21/core/serialization-filtering1.html",
    "https://codeql.github.com/codeql-query-help/python/py-unsafe-deserialization/"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://docs.python.org/3/library/pickle.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide"
     ],
     "strength": "unjudged"
    },
    {
     "id": "oracle",
     "name": "Oracle",
     "sources": [
      "https://docs.oracle.com/en/java/javase/21/core/serialization-filtering1.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://codeql.github.com/codeql-query-help/python/py-unsafe-deserialization/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-no-native-deserialise-untrusted",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-no-positive-tabindex",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "accessibility",
    "usability"
   ],
   "topics": [
    "frontend",
    "keyboard"
   ],
   "lang": "core",
   "applies_when": "Markup sets `tabindex`, or a layout reorders content with CSS.",
   "context": [
    "ui"
   ],
   "tool_gate": "ESLint `jsx-a11y/tabindex-no-positive`; axe `tabindex`.",
   "note": "4 orgs (W3C, Google, WebAIM, A11y Project); both tool-gate rule names checked against the jsx-a11y README and the axe rule list",
   "lacks": null,
   "rule": "Write the DOM in reading order and never set `tabindex` above 0.",
   "why": "A positive `tabindex` jumps those elements to the front of the tab order, ahead of everything else on the page. Each new control then needs a new number, and the order stops matching what the user sees. CSS that moves items visually without moving them in the DOM does the same thing.",
   "counterpoint": "\"The visual layout puts the sidebar first, but the main content comes first in the DOM.\" Reorder the DOM to match, or accept the difference only if the order still makes sense to a keyboard user. Do not patch it with numbers.",
   "fail": {
    "language": "html",
    "code": "<input name=\"last\" tabindex=\"2\">\n<input name=\"first\" tabindex=\"1\">\n<button tabindex=\"3\">Save</button>"
   },
   "pass": {
    "language": "html",
    "code": "<input name=\"first\">\n<input name=\"last\">\n<button>Save</button>\n<!-- tabindex=\"0\" only to add a custom widget to the order; tabindex=\"-1\" to focus it from script -->"
   },
   "sources": [
    "https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html",
    "https://web.dev/learn/accessibility/focus",
    "https://webaim.org/techniques/keyboard/",
    "https://www.a11yproject.com/checklist/"
   ],
   "orgs": [
    {
     "id": "w3c",
     "name": "W3C",
     "sources": [
      "https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://web.dev/learn/accessibility/focus"
     ],
     "strength": "unjudged"
    },
    {
     "id": "webaim",
     "name": "WebAIM",
     "sources": [
      "https://webaim.org/techniques/keyboard/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "a11yproject",
     "name": "The A11y Project",
     "sources": [
      "https://www.a11yproject.com/checklist/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-no-positive-tabindex",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-no-prototype-pollution",
   "level": "function",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "javascript",
    "injection"
   ],
   "lang": "core",
   "applies_when": "JavaScript or TypeScript code merges, clones, or assigns properties whose key names come from JSON, a query string, or other untrusted input.",
   "context": [],
   "tool_gate": "CodeQL `js/prototype-pollution-utility` and `js/prototype-polluting-assignment`.",
   "note": "ASVS 15.3.6; fn-no-mass-assignment covers the same input setting fields on a model instead of the prototype",
   "lacks": null,
   "rule": "Never write an untrusted key into a plain object. Use a `Map`, an object from `Object.create(null)`, or an allowlist of keys.",
   "why": "`obj[key] = value` with `key` set to `__proto__` writes to `Object.prototype`, so every object in the process gains that property. An attacker who sets `isAdmin` or a template option there can skip checks or reach code execution. The bug sits in generic helpers such as deep merge and path setters, so one bad helper exposes every caller. Blocking `__proto__`, `constructor`, and `prototype` inside a deep merge helps, but a blocklist misses obfuscated keys, so treat it as a stopgap.",
   "counterpoint": "\"Just freeze `Object.prototype` at startup.\" That helps, but it can break libraries that patch prototypes, and it leaves `constructor.prototype` paths in class instances open. Fix the merge, and add the freeze on top if your dependencies allow it.",
   "fail": {
    "language": "javascript",
    "code": "function merge(dst, src) {\n  for (const k in src) {\n    if (typeof src[k] === \"object\") merge(dst[k] ??= {}, src[k]);\n    else dst[k] = src[k];\n  }\n}\nmerge(settings, JSON.parse(body));   // body: {\"__proto__\": {\"isAdmin\": true}}"
   },
   "pass": {
    "language": "javascript",
    "code": "const ALLOWED = new Set([\"theme\", \"lang\"]);\nconst prefs = new Map();\nfor (const [k, v] of Object.entries(JSON.parse(body))) {\n  if (ALLOWED.has(k)) prefs.set(k, v);\n}"
   },
   "sources": [
    "https://raw.githubusercontent.com/OWASP/ASVS/master/5.0/en/0x24-V15-Secure-Coding-and-Architecture.md",
    "https://cheatsheetseries.owasp.org/cheatsheets/Prototype_Pollution_Prevention_Cheat_Sheet.html",
    "https://cwe.mitre.org/data/definitions/1321.html",
    "https://portswigger.net/web-security/prototype-pollution/preventing",
    "https://codeql.github.com/codeql-query-help/javascript/js-prototype-pollution-utility/",
    "https://learn.snyk.io/lesson/prototype-pollution/"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://raw.githubusercontent.com/OWASP/ASVS/master/5.0/en/0x24-V15-Secure-Coding-and-Architecture.md",
      "https://cheatsheetseries.owasp.org/cheatsheets/Prototype_Pollution_Prevention_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mitre",
     "name": "MITRE",
     "sources": [
      "https://cwe.mitre.org/data/definitions/1321.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "portswigger",
     "name": "PortSwigger",
     "sources": [
      "https://portswigger.net/web-security/prototype-pollution/preventing"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://codeql.github.com/codeql-query-help/javascript/js-prototype-pollution-utility/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "snyk",
     "name": "Snyk",
     "sources": [
      "https://learn.snyk.io/lesson/prototype-pollution/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-no-prototype-pollution",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-no-raw-html-sink",
   "level": "function",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "xss",
    "frontend"
   ],
   "lang": "core",
   "applies_when": "Code writes a string into a page, a template or a rendered model-output view.",
   "context": [
    "ui",
    "server"
   ],
   "tool_gate": "ESLint (`no-unsanitized/property`, `no-unsanitized/method`, `react/no-danger`) and template-engine autoescape checks.",
   "note": null,
   "lacks": null,
   "rule": "Write untrusted text through an encoding sink (`textContent`, auto-escaped template), never through a raw HTML sink.",
   "why": "One `innerHTML` of a username or of model output runs the attacker's script with the victim's session. Encoding must match the context: body, attribute, script string and URL each differ. Framework escape hatches such as `dangerouslySetInnerHTML` have the same effect.",
   "counterpoint": "\"We need to render rich text.\" Then sanitise with a maintained library such as DOMPurify at render time and add a CSP as a second layer. A CSP alone is not a fix, because it is defence in depth.",
   "fail": {
    "language": "text",
    "code": "el.innerHTML = comment.body;\n<div dangerouslySetInnerHTML={{ __html: reply }} />"
   },
   "pass": {
    "language": "text",
    "code": "el.textContent = comment.body;\n<div>{reply}</div>                       // escaped by the framework\n// user-authored HTML: DOMPurify.sanitize(html), nothing edited afterwards"
   },
   "sources": [
    "https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html",
    "https://genai.owasp.org/llmrisk/llm052025-improper-output-handling/",
    "https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML",
    "https://react.dev/reference/react-dom/components/common",
    "https://angular.dev/best-practices/security",
    "https://github.com/mozilla/eslint-plugin-no-unsanitized",
    "https://web.dev/articles/trusted-types",
    "https://www.cisa.gov/resources-tools/resources/secure-design-alert-eliminating-cross-site-scripting-vulnerabilities"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html",
      "https://genai.owasp.org/llmrisk/llm052025-improper-output-handling/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML",
      "https://github.com/mozilla/eslint-plugin-no-unsanitized"
     ],
     "strength": "unjudged"
    },
    {
     "id": "meta",
     "name": "Meta",
     "sources": [
      "https://react.dev/reference/react-dom/components/common"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://angular.dev/best-practices/security",
      "https://web.dev/articles/trusted-types"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cisa",
     "name": "CISA",
     "sources": [
      "https://www.cisa.gov/resources-tools/resources/secure-design-alert-eliminating-cross-site-scripting-vulnerabilities"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-no-raw-html-sink",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-no-shell-with-input",
   "level": "function",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "injection",
    "command"
   ],
   "lang": "core",
   "applies_when": "Code starts a process and some part of the command comes from a request, a file or model output.",
   "context": [],
   "tool_gate": "Bandit B602 (`subprocess_popen_with_shell_equals_true`) and B605 (`start_process_with_a_shell`) for Python.",
   "note": null,
   "lacks": null,
   "rule": "Start processes with the program and each argument as separate list items, and never through a shell string.",
   "why": "A shell reads `;`, `|` and `$()` as commands, so a filename of `a; rm -rf ~` runs. Argument arrays skip the shell. The values still need validation, since an argument beginning with `-` becomes an option; end options with `--` where the tool allows it.",
   "counterpoint": "\"We need shell features like pipes.\" Chain two `subprocess` calls in code, or use the library that does the job. If a shell is unavoidable, build the string only from constants.",
   "fail": {
    "language": "python",
    "code": "os.system(\"convert \" + upload_name + \" out.png\")\nsubprocess.run(f\"git log {branch}\", shell=True)"
   },
   "pass": {
    "language": "python",
    "code": "subprocess.run([\"convert\", upload_path, \"out.png\"], check=True)   # upload_path is a server-generated name\nsubprocess.run([\"git\", \"log\", branch], check=True)   # branch checked against an allowlist"
   },
   "sources": [
    "https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html",
    "https://genai.owasp.org/llmrisk/llm052025-improper-output-handling/",
    "https://docs.python.org/3/library/subprocess.html",
    "https://pkg.go.dev/os/exec",
    "https://www.cisa.gov/resources-tools/resources/secure-design-alert-eliminating-os-command-injection-vulnerabilities"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html",
      "https://genai.owasp.org/llmrisk/llm052025-improper-output-handling/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://docs.python.org/3/library/subprocess.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://pkg.go.dev/os/exec"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cisa",
     "name": "CISA",
     "sources": [
      "https://www.cisa.gov/resources-tools/resources/secure-design-alert-eliminating-os-command-injection-vulnerabilities"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-no-shell-with-input",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-object-level-authz",
   "level": "function",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "authz"
   ],
   "lang": "core",
   "applies_when": "A handler loads, changes or deletes a record by an ID that comes from the request.",
   "context": [
    "server"
   ],
   "tool_gate": "Authorization tests that call each endpoint as a second user; no static gate catches a missing check.",
   "note": null,
   "lacks": null,
   "rule": "Check that the caller may act on the specific record before returning or changing it.",
   "why": "A login proves who the caller is, not which records they own. Without a per-object check, changing `id=41` to `id=42` reads or edits another user's data (IDOR, also called BOLA). Random IDs only hide the problem.",
   "counterpoint": "\"The IDs are UUIDs, nobody can guess them.\" A UUID leaks through logs, links and shared screenshots, and it is not an access check. Keep it as a second layer. Scope the query itself (`WHERE owner_id = :me`) so the check cannot be forgotten on the next endpoint.",
   "fail": {
    "language": "text",
    "code": "order = db.get(Order, request.args[\"id\"])     # any logged-in user, any order\nreturn order.to_json()"
   },
   "pass": {
    "language": "text",
    "code": "order = db.get(Order, request.args[\"id\"])\nif order is None or order.owner_id != user.id: raise NotFound()   # same error for both\nreturn order.to_json()"
   },
   "sources": [
    "https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html",
    "https://api-security.owasp.org/editions/2023/en/0xa1-broken-object-level-authorization",
    "https://top10.owasp.org/2025/A01_2025-Broken_Access_Control",
    "https://learn.microsoft.com/en-us/aspnet/core/security/authorization/resourcebased",
    "https://developers.cloudflare.com/api-shield/security/bola-vulnerability-detection/",
    "https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-228-upd1.pdf"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html",
      "https://api-security.owasp.org/editions/2023/en/0xa1-broken-object-level-authorization",
      "https://top10.owasp.org/2025/A01_2025-Broken_Access_Control"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/aspnet/core/security/authorization/resourcebased"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cloudflare",
     "name": "Cloudflare",
     "sources": [
      "https://developers.cloudflare.com/api-shield/security/bola-vulnerability-detection/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nist",
     "name": "NIST",
     "sources": [
      "https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-228-upd1.pdf"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-object-level-authz",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-parameter-limit",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "readability",
    "api"
   ],
   "lang": "core",
   "applies_when": "A function or constructor signature takes more parameters than the project's limit allows, or the project has no limit yet.",
   "context": [],
   "tool_gate": "ESLint max-params (default 3), Pylint too-many-arguments (R0913, max-args default 5) and too-many-positional-arguments (R0917, default 5), Checkstyle ParameterNumber (default 7), RuboCop Metrics/ParameterLists (default 5), PMD ExcessiveParameterList (minimum default 10).",
   "note": "the defaults range from 3 to 10, so the card asks for the project's limit; Checkstyle lets a project ignore an overridden method whose signature it does not control",
   "lacks": null,
   "rule": "Set a parameter-count limit in the project's linter, and keep signatures within it.",
   "why": "Every positional value is a fact the caller must remember and a slot that can take the wrong one, especially when several share a type. `create_user(name, email, street, city, zip, country, plan, trial_days)` cannot be read at the call site and is easy to call in the wrong order. A named record or a builder gives the values names and lets the compiler and the reader check them. Group only values that already travel together, and keep required fields required.",
   "counterpoint": "\"A parameter object just moves the fields into another class and can hide required ones.\" A record that groups values with one owner is not hiding them, and a builder that names each required value at the call site is clearer than eight slots. The failure is a bag object with optional fields that may be missing. Do not introduce one. A signature the author cannot change, such as an override of a third-party method, is exempt, because the parameter count is not the author's decision. Keyword-only parameters fix the call-site order problem but still count toward the limit, so group the values that travel together.",
   "fail": {
    "language": "python",
    "code": "def create_user(name, email, street, city, zip, country, plan, trial_days):\n    ..."
   },
   "pass": {
    "language": "python",
    "code": "def create_user(contact: Contact, subscription: Subscription):\n    ...\n\n# or: User.builder().name(...).email(...).plan(...).build()"
   },
   "sources": [
    "https://eslint.org/docs/latest/rules/max-params",
    "https://checkstyle.sourceforge.io/checks/sizes/parameternumber.html",
    "https://pylint.readthedocs.io/en/stable/user_guide/configuration/all-options.html",
    "https://pylint.readthedocs.io/en/stable/user_guide/messages/refactor/too-many-arguments.html",
    "https://docs.rubocop.org/rubocop/latest/cops_metrics.html",
    "https://pmd.github.io/pmd/pmd_rules_java_design.html"
   ],
   "orgs": [
    {
     "id": "eslint",
     "name": "ESLint",
     "sources": [
      "https://eslint.org/docs/latest/rules/max-params"
     ],
     "strength": "unjudged"
    },
    {
     "id": "checkstyle",
     "name": "Checkstyle",
     "sources": [
      "https://checkstyle.sourceforge.io/checks/sizes/parameternumber.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "pylint",
     "name": "Pylint",
     "sources": [
      "https://pylint.readthedocs.io/en/stable/user_guide/configuration/all-options.html",
      "https://pylint.readthedocs.io/en/stable/user_guide/messages/refactor/too-many-arguments.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "rubocop",
     "name": "RuboCop",
     "sources": [
      "https://docs.rubocop.org/rubocop/latest/cops_metrics.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "pmd",
     "name": "PMD",
     "sources": [
      "https://pmd.github.io/pmd/pmd_rules_java_design.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-parameter-limit",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-password-slow-hash",
   "level": "function",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "authn",
    "crypto"
   ],
   "lang": "core",
   "applies_when": "Code stores or verifies a user password.",
   "context": [
    "server"
   ],
   "tool_gate": "Grep or SAST rule for `md5`, `sha1`, `sha256` near a `password` identifier.",
   "note": null,
   "lacks": null,
   "rule": "Hash passwords with Argon2id (or scrypt, bcrypt, PBKDF2) and a per-password salt from the library.",
   "why": "A fast hash such as SHA-256 lets an attacker with the database try billions of guesses a second. A slow, salted, memory-hard hash makes each guess cost real time. A common baseline is Argon2id with at least 19 MiB of memory and 2 iterations. Check the current numbers when you review.",
   "counterpoint": "\"Argon2 is expensive at login.\" That cost is the defence; tune it to about a second on production hardware. Where FIPS compliance rules Argon2 out, PBKDF2-HMAC-SHA256 with a high iteration count is the accepted fallback.",
   "fail": {
    "language": "text",
    "code": "stored = hashlib.sha256(password.encode()).hexdigest()"
   },
   "pass": {
    "language": "text",
    "code": "ph = argon2.PasswordHasher()            # salt and parameters stored in the hash string\nstored = ph.hash(password)\nph.verify(stored, attempt)             # rehash when check_needs_rehash()"
   },
   "sources": [
    "https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html",
    "https://top10.owasp.org/2025/A04_2025-Cryptographic_Failures",
    "https://www.rfc-editor.org/rfc/rfc9106.html",
    "https://pages.nist.gov/800-63-4/sp800-63b.html"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html",
      "https://top10.owasp.org/2025/A04_2025-Cryptographic_Failures"
     ],
     "strength": "unjudged"
    },
    {
     "id": "ietf",
     "name": "IETF",
     "sources": [
      "https://www.rfc-editor.org/rfc/rfc9106.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nist",
     "name": "NIST",
     "sources": [
      "https://pages.nist.gov/800-63-4/sp800-63b.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-password-slow-hash",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-redirect-target-allowlist",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "security"
   ],
   "topics": [
    "redirect",
    "url"
   ],
   "lang": "core",
   "applies_when": "A handler redirects to a URL taken from a query parameter, form field, header, or stored value, such as `?next=` after login.",
   "context": [
    "server"
   ],
   "tool_gate": "CodeQL `py/url-redirection`; ASP.NET Core `LocalRedirect` or `Url.IsLocalUrl`.",
   "note": "ASVS 3.7.2 (allowlisted redirect hosts) and 1.2.2 (safe URL schemes); sys-ssrf covers the server following redirects, not sending users to them",
   "lacks": null,
   "rule": "Redirect only to a local path or to a host on a server-side allowlist, never to a URL taken from the request as is.",
   "why": "A link such as `https://bank.example/login?next=https://evil.example` carries your domain, so users and mail filters trust it, and after login the user lands on a phishing page. When page script sets `location` from the same value, a `javascript:` URL runs as script. Parse the URL and compare its scheme and host. A `startswith(\"/\")` check alone passes `//evil.example`, and browsers read `/\\evil.example` the same way. Better still, accept a short name and map it to the URL on the server.",
   "counterpoint": "\"The redirect only matters after a successful login.\" That is when it hurts most: the user has just typed a password and trusts the next page. A phishing copy of the login form on that page collects the password a second time.",
   "fail": {
    "language": "python",
    "code": "return redirect(request.args[\"next\"])"
   },
   "pass": {
    "language": "python",
    "code": "target = urlparse(request.args.get(\"next\", \"/\"))\nif target.scheme or target.netloc or not target.path.startswith(\"/\") or \"\\\\\" in target.path:\n    target = urlparse(\"/\")\nreturn redirect(target.path)"
   },
   "sources": [
    "https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html",
    "https://cwe.mitre.org/data/definitions/601.html",
    "https://learn.microsoft.com/en-us/aspnet/core/security/preventing-open-redirects",
    "https://portswigger.net/kb/issues/00500100_open-redirection-reflected",
    "https://codeql.github.com/codeql-query-help/python/py-url-redirection/"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mitre",
     "name": "MITRE",
     "sources": [
      "https://cwe.mitre.org/data/definitions/601.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/aspnet/core/security/preventing-open-redirects"
     ],
     "strength": "unjudged"
    },
    {
     "id": "portswigger",
     "name": "PortSwigger",
     "sources": [
      "https://portswigger.net/kb/issues/00500100_open-redirection-reflected"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://codeql.github.com/codeql-query-help/python/py-url-redirection/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-redirect-target-allowlist",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-reduced-motion-respected",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "accessibility",
    "usability"
   ],
   "topics": [
    "frontend",
    "css",
    "motion"
   ],
   "lang": "core",
   "applies_when": "Styles or script add a transition, an animation, a parallax effect, or autoplay motion.",
   "context": [
    "ui"
   ],
   "tool_gate": "Stylelint `a11y/media-prefers-reduced-motion` from the `stylelint-a11y` plugin. A browser run with reduced motion emulated.",
   "note": "WCAG 2.3.3 is level AAA, so this is good practice and not an AA floor",
   "lacks": null,
   "rule": "Switch off or replace large, repeating, or interaction-triggered motion when `prefers-reduced-motion: reduce` is set.",
   "why": "Scaling, panning, and parallax can trigger nausea and dizziness for people with vestibular disorders. They have already told their device so. A page that ignores the setting makes them leave.",
   "counterpoint": "\"Motion carries meaning, such as a loading spinner.\" Keep what the user needs and make it gentler: swap movement for a fade. The rule is about decorative and large movement, not about every pixel that moves.",
   "fail": {
    "language": "css",
    "code": ".hero   { animation: zoom-in 1.2s ease-out infinite alternate; }\n.card   { transition: transform 0.6s; }\n.card:hover { transform: scale(1.3) rotate(4deg); }"
   },
   "pass": {
    "language": "css",
    "code": ".hero { animation: zoom-in 1.2s ease-out infinite alternate; }\n@media (prefers-reduced-motion: reduce) {\n  .hero { animation: none; }\n  .card { transition: opacity 0.2s; }\n  .card:hover { transform: none; opacity: 0.9; }\n}"
   },
   "sources": [
    "https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion",
    "https://www.w3.org/WAI/WCAG22/Understanding/animation-from-interactions.html",
    "https://developer.apple.com/tutorials/data/design/human-interface-guidelines/accessibility.json",
    "https://www.a11yproject.com/checklist/",
    "https://web.dev/articles/cls"
   ],
   "orgs": [
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion"
     ],
     "strength": "unjudged"
    },
    {
     "id": "w3c",
     "name": "W3C",
     "sources": [
      "https://www.w3.org/WAI/WCAG22/Understanding/animation-from-interactions.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "apple",
     "name": "Apple",
     "sources": [
      "https://developer.apple.com/tutorials/data/design/human-interface-guidelines/accessibility.json"
     ],
     "strength": "unjudged"
    },
    {
     "id": "a11yproject",
     "name": "The A11y Project",
     "sources": [
      "https://www.a11yproject.com/checklist/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://web.dev/articles/cls"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-reduced-motion-respected",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-regex-compiled-once",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "performance"
   ],
   "topics": [],
   "lang": "core",
   "applies_when": "Code builds a regular expression or similar compiled matcher inside a loop or a function that runs per request.",
   "context": [],
   "tool_gate": "Linter rule for a regex constructor in a loop, where the language has one.",
   "note": "4 orgs back it; only the Rust docs state the loop rule outright, and Python and .NET cache recent patterns, so a few module-level calls are not a failure",
   "lacks": null,
   "rule": "Compile a regular expression once, at module or startup level, and reuse it.",
   "why": "Compiling costs far more than matching and throws away reusable allocations. Inside a loop or a per-request function, the cost repeats on every call.",
   "counterpoint": "\"The library caches patterns.\" Python and .NET keep recently used patterns, so a few static calls are fine. A pattern built from input or an unbounded set of patterns defeats the cache.",
   "fail": {
    "language": "go",
    "code": "func valid(s string) bool {\n\treturn regexp.MustCompile(`^[a-z]+$`).MatchString(s)   // compiled per call\n}"
   },
   "pass": {
    "language": "go",
    "code": "var lower = regexp.MustCompile(`^[a-z]+$`)\n\nfunc valid(s string) bool { return lower.MatchString(s) }"
   },
   "sources": [
    "https://docs.rs/regex/latest/regex/",
    "https://pkg.go.dev/regexp",
    "https://docs.python.org/3/library/re.html",
    "https://learn.microsoft.com/en-us/dotnet/standard/base-types/best-practices-regex"
   ],
   "orgs": [
    {
     "id": "rust",
     "name": "Rust project",
     "sources": [
      "https://docs.rs/regex/latest/regex/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://pkg.go.dev/regexp"
     ],
     "strength": "unjudged"
    },
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://docs.python.org/3/library/re.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/dotnet/standard/base-types/best-practices-regex"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-regex-compiled-once",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-related-controls-in-fieldset",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "accessibility",
    "usability"
   ],
   "topics": [
    "frontend",
    "forms",
    "html"
   ],
   "lang": "core",
   "applies_when": "Markup or a component renders a radio group, a checkbox group, or a value split across several inputs, such as a date or an address.",
   "context": [
    "ui"
   ],
   "tool_gate": null,
   "note": "4 orgs (GOV.UK, W3C, USWDS, A11y Project). NN/g asks to tag groups for screen readers but does not name fieldset, so it does not count. No source gives a spacing threshold, so spacing stays out of the Rule. Only GOV.UK says the legend comes first, so that sits in Why",
   "lacks": null,
   "rule": "Wrap each radio group, checkbox group, and multi-part value, such as day, month, and year, in a `<fieldset>` with a `<legend>` that names the group, often as the question.",
   "why": "Each radio's own label says only \"Yes\" or \"No\". Without a group, a screen reader user hears the option but not the question it answers. Put the `legend` first inside the `fieldset`. It acts as the group's heading, so assistive technology can tie each option to its question. The same holds for a date split into three boxes labelled \"Day\", \"Month\", and \"Year\". The visual group needs a matching code group, or the two drift apart.",
   "counterpoint": "\"A `fieldset` draws a border we don't want.\" Remove the border in CSS with `border: 0; padding: 0; margin: 0`. The grouping still reaches assistive technology. A `div` with `role=\"group\"` and `aria-labelledby` is a fallback for a design system that cannot render a `fieldset`. VoiceOver on iOS ignores `fieldset` and `legend`. That is a reason to add `aria-labelledby` on each input as well, not to drop the group. A single checkbox, such as \"I agree\", needs no `fieldset`.",
   "fail": {
    "language": "html",
    "code": "<p>Do you have a passport?</p>\n<input type=\"radio\" id=\"pp-yes\" name=\"passport\" value=\"yes\">\n<label for=\"pp-yes\">Yes</label>\n<input type=\"radio\" id=\"pp-no\" name=\"passport\" value=\"no\">\n<label for=\"pp-no\">No</label>"
   },
   "pass": {
    "language": "html",
    "code": "<fieldset>\n  <legend>Do you have a passport?</legend>\n  <input type=\"radio\" id=\"pp-yes\" name=\"passport\" value=\"yes\">\n  <label for=\"pp-yes\">Yes</label>\n  <input type=\"radio\" id=\"pp-no\" name=\"passport\" value=\"no\">\n  <label for=\"pp-no\">No</label>\n</fieldset>"
   },
   "sources": [
    "https://design-system.service.gov.uk/components/fieldset/",
    "https://design-system.service.gov.uk/components/date-input/",
    "https://www.w3.org/WAI/tutorials/forms/grouping/",
    "https://designsystem.digital.gov/components/form/",
    "https://www.a11yproject.com/checklist/"
   ],
   "orgs": [
    {
     "id": "govuk",
     "name": "GOV.UK",
     "sources": [
      "https://design-system.service.gov.uk/components/fieldset/",
      "https://design-system.service.gov.uk/components/date-input/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "w3c",
     "name": "W3C",
     "sources": [
      "https://www.w3.org/WAI/tutorials/forms/grouping/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "uswds",
     "name": "USWDS",
     "sources": [
      "https://designsystem.digital.gov/components/form/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "a11yproject",
     "name": "The A11y Project",
     "sources": [
      "https://www.a11yproject.com/checklist/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-related-controls-in-fieldset",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-rerun-safe-after-crash",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "reliability"
   ],
   "topics": [
    "idempotency"
   ],
   "lang": "core",
   "applies_when": "A function or job changes state and can be run twice, or can be killed partway.",
   "context": [],
   "tool_gate": null,
   "note": "second run - cursor/plugins, Uber, Stripe, Dropbox, GitHub, Netflix; crash at any step - cursor/plugins, Stripe, Dropbox (workers die mid-task and retries must stay correct), Netflix; derive from current state - cursor/plugins, Stripe, Netflix, GitHub; leftovers recognisable - cursor/plugins, Netflix (a marker tied to the backup timestamp, a size check against the manifest), GitHub (a review records done only after every removal succeeds)",
   "lacks": null,
   "rule": "Make a state-changing operation give the same result on a second run and after a crash at any step, by deriving each step from current state and making leftovers recognisable as stale.",
   "why": "Jobs get retried and processes get killed. An append, a counter bump, or a lock file with no owner and time leaves the next run unable to tell finished work from half-done work. Record \"done\" only after the last step succeeds, so a crash leaves the work visibly pending.",
   "counterpoint": "\"Retries are rare.\" Crashes are not. For API calls that cross a network, [[svc-idempotency-key]] covers the key-based form of this rule.",
   "fail": {
    "language": "python",
    "code": "cur.execute(\"UPDATE accounts SET balance = balance + %s WHERE id = %s\", (amount, acct))  # a retry applies it twice\nopen(\"out.json\", \"w\").write(data)                                                       # a crash leaves a truncated file"
   },
   "pass": {
    "language": "python",
    "code": "cur.execute(\"INSERT INTO applied_ops (op_id) VALUES (%s) ON CONFLICT DO NOTHING\", (op_id,))\nif cur.rowcount == 1:  # first run of this operation; same transaction as the update\n    cur.execute(\"UPDATE accounts SET balance = balance + %s WHERE id = %s\", (amount, acct))\ntmp = path.with_suffix(\".tmp\")\ntmp.write_text(data)\nos.replace(tmp, path)  # the reader sees the old file or the new one, never half of one"
   },
   "sources": [
    "https://github.com/cursor/plugins/blob/main/pstack/skills/principle-make-operations-idempotent/SKILL.md",
    "https://www.uber.com/blog/high-throughput-processing/",
    "https://stripe.dev/blog/how-stripe-uses-graph-search-and-state-machines-to-auto-remediate-a-global-database-fleet",
    "https://dropbox.tech/infrastructure/asynchronous-task-scheduling-at-dropbox",
    "https://netflixtechblog.com/timeseries-tiered-storage-journey-kafka-flink-streams-to-native-cassandra-cold-reads-e59d597c9d60",
    "https://github.blog/engineering/platform-security/staying-evergreen-how-we-automated-secure-access-and-repo-hygiene-in-a-shared-org"
   ],
   "orgs": [
    {
     "id": "cursor",
     "name": "Cursor",
     "sources": [
      "https://github.com/cursor/plugins/blob/main/pstack/skills/principle-make-operations-idempotent/SKILL.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "uber",
     "name": "Uber",
     "sources": [
      "https://www.uber.com/blog/high-throughput-processing/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "stripe",
     "name": "Stripe",
     "sources": [
      "https://stripe.dev/blog/how-stripe-uses-graph-search-and-state-machines-to-auto-remediate-a-global-database-fleet"
     ],
     "strength": "unjudged"
    },
    {
     "id": "dropbox",
     "name": "Dropbox",
     "sources": [
      "https://dropbox.tech/infrastructure/asynchronous-task-scheduling-at-dropbox"
     ],
     "strength": "unjudged"
    },
    {
     "id": "netflix",
     "name": "Netflix",
     "sources": [
      "https://netflixtechblog.com/timeseries-tiered-storage-journey-kafka-flink-streams-to-native-cassandra-cold-reads-e59d597c9d60"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://github.blog/engineering/platform-security/staying-evergreen-how-we-automated-secure-access-and-repo-hygiene-in-a-shared-org"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-rerun-safe-after-crash",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-response-fields-explicit",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "security",
    "compatibility"
   ],
   "topics": [
    "api",
    "data"
   ],
   "lang": "core",
   "applies_when": "A handler returns a stored object or entity to the client.",
   "context": [
    "server"
   ],
   "tool_gate": "Response-schema tests that fail on unlisted fields.",
   "note": null,
   "lacks": null,
   "rule": "Return a response type that lists each field, never the stored model or a generic dump of it.",
   "why": "A model gains `password_hash`, `internal_notes` or `email` and every endpoint that serialises it leaks the new field silently. Callers also start to depend on whatever is sent, so removing a field later is a breaking change. An explicit response type makes each exposed field a decision someone reviewed. The write side is [[fn-no-mass-assignment]].",
   "counterpoint": "\"A response type per endpoint duplicates the model.\" Derive it from the model with a field list. The duplication is one line per field and shows up in the diff when a field is added.",
   "fail": {
    "language": "text",
    "code": "return jsonify(user.__dict__)\nreturn user_model.to_dict()          # all columns"
   },
   "pass": {
    "language": "text",
    "code": "class UserOut(BaseModel): id: int; display_name: str\nreturn UserOut.model_validate(user)"
   },
   "sources": [
    "https://api-security.owasp.org/editions/2023/en/0xa3-broken-object-property-level-authorization",
    "https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-web-api",
    "https://www.django-rest-framework.org/api-guide/serializers/",
    "https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-228-upd1.pdf"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://api-security.owasp.org/editions/2023/en/0xa3-broken-object-property-level-authorization"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-web-api"
     ],
     "strength": "unjudged"
    },
    {
     "id": "drf",
     "name": "Django REST framework",
     "sources": [
      "https://www.django-rest-framework.org/api-guide/serializers/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nist",
     "name": "NIST",
     "sources": [
      "https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-228-upd1.pdf"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-response-fields-explicit",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-specific-exceptions-only",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "correctness"
   ],
   "topics": [
    "errors"
   ],
   "lang": "core",
   "applies_when": "Code catches exceptions in business logic.",
   "context": [],
   "tool_gate": "ruff BLE001 / E722 (Python)",
   "note": null,
   "lacks": null,
   "rule": "Catch only the specific exception you can handle in business code.",
   "why": "A blanket catch treats a mistyped attribute the same as a network blip. Real bugs get swallowed, logged as a warning, and retried. [[py-ruff-gate]] enforces this.",
   "counterpoint": "\"A worker loop must not die on one bad message.\" That is the one legitimate blanket catch: once, at the process boundary, with the traceback logged, playing the role that a web framework's error middleware plays for routes. Everywhere else, catch specific exceptions. The other exception is a security boundary: catch broadly there, log redacted, and deny (fail closed). See [[sys-secrets-and-errors]].",
   "fail": {
    "language": "text",
    "code": "try:\n    charge(order)\nexcept Exception:\n    return None"
   },
   "pass": {
    "language": "text",
    "code": "try:\n    charge(order)\nexcept CardDeclined as err:\n    raise PaymentDeclined(order.id) from err"
   },
   "sources": [
    "https://go.dev/wiki/CodeReviewComments",
    "https://dev.to/sportebois/software-design-red-flags-wisdom-nuggets-from-john-ousterhout-43i2",
    "https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1031",
    "https://peps.python.org/pep-0008/"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://go.dev/wiki/CodeReviewComments"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1031"
     ],
     "strength": "supports"
    },
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://peps.python.org/pep-0008/"
     ],
     "strength": "supports"
    }
   ],
   "people": [
    {
     "id": "portebois",
     "name": "Sébastien Portebois",
     "sources": [
      "https://dev.to/sportebois/software-design-red-flags-wisdom-nuggets-from-john-ousterhout-43i2"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "mixed",
   "url": "https://goodcodereview.com/function#fn-specific-exceptions-only",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-status-announced-live-region",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "accessibility",
    "usability"
   ],
   "topics": [
    "frontend",
    "aria"
   ],
   "lang": "core",
   "applies_when": "Script shows a message after an async action without moving focus, such as \"Saved\", a result count, or an error.",
   "context": [
    "ui"
   ],
   "tool_gate": "none; a screen-reader pass, or a test that the region exists with a role before the text changes.",
   "note": "3 orgs (W3C, Mozilla, WebAIM); W3C technique ARIA22 is the W3C source for the region existing first, and MDN says role=\"alert\" is announced even when injected. USWDS reports that VoiceOver on macOS does not support `role=\"alert\"`, so test the target reader",
   "lacks": null,
   "rule": "Announce an async status message through a live region (`role=\"status\"` or `role=\"alert\"`), and put a `role=\"status\"` or `aria-live` region in the markup before its text changes.",
   "why": "A sighted user sees \"Saved\" appear. A screen-reader user hears nothing, because focus did not move. Most readers announce a change only in a region that existed first, so a status region created together with its message is often skipped. A `role=\"alert\"` region is the exception: readers announce it even when script injects it.",
   "counterpoint": "\"Move focus to the message instead.\" Use that for a blocking error after submit, where the user must act. For a passive result, moving focus steals the user's place. Keep `assertive` and `role=\"alert\"` for errors, because they interrupt speech.",
   "fail": {
    "language": "js",
    "code": "const note = document.createElement(\"div\");\nnote.textContent = \"Changes saved\";\ndocument.body.append(note);"
   },
   "pass": {
    "language": "html",
    "code": "<div id=\"status\" role=\"status\" aria-live=\"polite\"></div>\n<script>\n  document.getElementById(\"status\").textContent = \"Changes saved\";\n</script>"
   },
   "sources": [
    "https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html",
    "https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Guides/Live_regions",
    "https://inclusive-components.design/notifications/",
    "https://www.w3.org/WAI/tutorials/forms/notifications/",
    "https://www.w3.org/WAI/WCAG22/Techniques/aria/ARIA22",
    "https://webaim.org/techniques/aria/"
   ],
   "orgs": [
    {
     "id": "w3c",
     "name": "W3C",
     "sources": [
      "https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html",
      "https://www.w3.org/WAI/tutorials/forms/notifications/",
      "https://www.w3.org/WAI/WCAG22/Techniques/aria/ARIA22"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Guides/Live_regions"
     ],
     "strength": "unjudged"
    },
    {
     "id": "webaim",
     "name": "WebAIM",
     "sources": [
      "https://webaim.org/techniques/aria/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "heydon",
     "name": "Heydon Pickering",
     "sources": [
      "https://inclusive-components.design/notifications/"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-status-announced-live-region",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-target-size",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "accessibility",
    "usability"
   ],
   "topics": [
    "frontend",
    "css"
   ],
   "lang": "core",
   "applies_when": "Styles size a button, icon button, checkbox, or link that a pointer or finger operates.",
   "context": [
    "ui"
   ],
   "tool_gate": "axe `target-size` (WCAG 2.2); a rendered check for the spacing exception.",
   "note": "3 orgs for the 24 CSS px floor and the spacing exception (W3C, Deque, Mozilla); Apple (44 pt) and web.dev (48 px) ask for more; the Material 48 dp page was not readable",
   "lacks": null,
   "rule": "Make each pointer target at least 24 by 24 CSS pixels, or space it so that a 24 px circle centred on it touches no other target and no other undersized target's circle.",
   "why": "Small, tight controls cause mis-taps for anyone with a tremor, a large thumb, or a bumpy train. A mis-tap on \"Delete\" next to \"Edit\" costs more than a mis-tap on a menu item.",
   "counterpoint": "\"Inline links and dense tables are exempt.\" Targets inside a sentence are, and so are controls with an equivalent control that is big enough. A row of toolbar icons is neither, and nor are the action buttons on a table row. Touch platforms often ask for 44 px or more, so treat 24 px as the floor, not the goal.",
   "fail": {
    "language": "css",
    "code": ".icon-btn { width: 16px; height: 16px; margin: 0; }"
   },
   "pass": {
    "language": "css",
    "code": ".icon-btn { min-width: 24px; min-height: 24px; padding: 4px; }"
   },
   "sources": [
    "https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html",
    "https://developer.apple.com/tutorials/data/design/human-interface-guidelines/accessibility.json",
    "https://www.a11yproject.com/checklist/",
    "https://dequeuniversity.com/rules/axe/html/4.10/target-size",
    "https://developer.mozilla.org/en-US/docs/Web/Accessibility/Guides/Understanding_WCAG/Operable"
   ],
   "orgs": [
    {
     "id": "w3c",
     "name": "W3C",
     "sources": [
      "https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "apple",
     "name": "Apple",
     "sources": [
      "https://developer.apple.com/tutorials/data/design/human-interface-guidelines/accessibility.json"
     ],
     "strength": "unjudged"
    },
    {
     "id": "a11yproject",
     "name": "The A11y Project",
     "sources": [
      "https://www.a11yproject.com/checklist/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "deque",
     "name": "Deque",
     "sources": [
      "https://dequeuniversity.com/rules/axe/html/4.10/target-size"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://developer.mozilla.org/en-US/docs/Web/Accessibility/Guides/Understanding_WCAG/Operable"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-target-size",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-test-behaviour-not-implementation",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "testing",
    "maintainability"
   ],
   "topics": [
    "tests"
   ],
   "lang": "core",
   "applies_when": "A test exercises a function, class, or module with callers.",
   "context": [],
   "tool_gate": "Refactor the code without changing what it does and run the tests; mutation testing shows whether they still catch real faults.",
   "note": "Google (Software Engineering at Google, ch. 12), Microsoft Learn (.NET unit-testing best practices), Spotify engineering. Spotify keeps tests of internals for isolated, complex logic; the Rust book calls testing private functions a debate.",
   "lacks": null,
   "rule": "Assert what a caller can observe through the public interface, so a change that keeps behaviour does not break the test.",
   "why": "A test tied to private calls, call order, or internal state fails when the code is restructured though nothing a caller sees changed. People then edit tests to match the code, and the suite stops protecting behaviour. It also pins whatever shape the code has today, including shapes that should change.",
   "counterpoint": "\"Some internals are complex enough to test on their own.\" Then give that logic its own unit with a public interface and test it through that. Testing a private function directly is a debated choice, so say why in the test.",
   "fail": {
    "language": "python",
    "code": "def test_checkout():\n    cart = Cart()\n    cart.add(Item(\"pen\", 300))\n    cart._recalculate_totals = Mock()          # private call\n    cart.checkout()\n    cart._recalculate_totals.assert_called_once()"
   },
   "pass": {
    "language": "python",
    "code": "def test_checkout_charges_the_sum_of_item_prices():\n    cart = Cart()\n    cart.add(Item(\"pen\", 300))\n    cart.add(Item(\"book\", 450))\n    assert cart.checkout().total == 750"
   },
   "sources": [
    "https://abseil.io/resources/swe-book/html/ch12.html",
    "https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices",
    "https://engineering.atspotify.com/2018/01/11/testing-of-microservices",
    "https://testdesiderata.com/",
    "https://kentcdodds.com/blog/testing-implementation-details",
    "https://doc.rust-lang.org/book/ch11-03-test-organization.html"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://abseil.io/resources/swe-book/html/ch12.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices"
     ],
     "strength": "unjudged"
    },
    {
     "id": "spotify",
     "name": "Spotify",
     "sources": [
      "https://engineering.atspotify.com/2018/01/11/testing-of-microservices"
     ],
     "strength": "unjudged"
    },
    {
     "id": "rust",
     "name": "Rust project",
     "sources": [
      "https://doc.rust-lang.org/book/ch11-03-test-organization.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "kent-beck",
     "name": "Kent Beck",
     "sources": [
      "https://testdesiderata.com/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "kent-c-dodds",
     "name": "Kent C. Dodds",
     "sources": [
      "https://kentcdodds.com/blog/testing-implementation-details"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-test-behaviour-not-implementation",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-test-oracle-independent",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "correctness",
    "testing"
   ],
   "topics": [
    "tests"
   ],
   "lang": "core",
   "applies_when": "A test asserts on a computed value.",
   "context": [],
   "tool_gate": "Mutation testing.",
   "note": "Google, Microsoft and Cursor each say to write the expected value out in the test, not compute it with logic that can share the bug",
   "lacks": null,
   "rule": "Take a test's expected value from the requirement, not from the code under test.",
   "why": "An expected value computed by the code under test passes when the code is wrong. Likewise, a \"not called\" assertion passes when the spy is wired to the wrong target, so pair it with a case where the call happens.",
   "counterpoint": "\"Recomputing keeps the test short.\" It also repeats the bug. Use a literal from the spec or a worked example.",
   "fail": {
    "language": "python",
    "code": "assert total(items) == sum(i.price for i in items)   # re-implements the code"
   },
   "pass": {
    "language": "python",
    "code": "assert total([Item(price=300), Item(price=450)]) == 750"
   },
   "sources": [
    "https://github.com/cursor/plugins/blob/main/pstack/skills/principle-test-behavior-not-implementation/SKILL.md",
    "https://abseil.io/resources/swe-book/html/ch12.html",
    "https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices",
    "https://swehb.nasa.gov/display/SWEHBVD/5.14+-+Test+-+Software+Test+Procedures",
    "https://docs.gitlab.com/ee/development/testing_guide/frontend_testing.html"
   ],
   "orgs": [
    {
     "id": "cursor",
     "name": "Cursor",
     "sources": [
      "https://github.com/cursor/plugins/blob/main/pstack/skills/principle-test-behavior-not-implementation/SKILL.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://abseil.io/resources/swe-book/html/ch12.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nasa",
     "name": "NASA",
     "sources": [
      "https://swehb.nasa.gov/display/SWEHBVD/5.14+-+Test+-+Software+Test+Procedures"
     ],
     "strength": "supports"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://docs.gitlab.com/ee/development/testing_guide/frontend_testing.html"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/function#fn-test-oracle-independent",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-tls-verify-on",
   "level": "function",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "tls",
    "network"
   ],
   "lang": "core",
   "applies_when": "Code makes an outbound HTTPS, database or message-broker connection.",
   "context": [],
   "tool_gate": "Bandit B501 (`request_with_no_cert_validation`), CodeQL `py/request-without-cert-validation`; a grep for `rejectUnauthorized: false` and `InsecureSkipVerify: true`.",
   "note": null,
   "lacks": null,
   "rule": "Leave certificate and hostname verification on in every environment that ships.",
   "why": "With verification off, any on-path attacker presents a certificate and reads or changes the traffic. The flag is usually added to silence a dev-box error and then ships. Fix the trust store or pin a private CA instead.",
   "counterpoint": "\"It is an internal network.\" Internal links get sniffed after one compromised host. A private CA gives you the same convenience with the check intact.",
   "fail": {
    "language": "text",
    "code": "requests.get(url, verify=False)                  # Python\nhttps.request({ rejectUnauthorized: false })     // Node.js"
   },
   "pass": {
    "language": "python",
    "code": "requests.get(url, verify=\"/etc/ssl/company-ca.pem\")   # private CA added, check stays on"
   },
   "sources": [
    "https://top10.owasp.org/2025/A04_2025-Cryptographic_Failures",
    "https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Security_Cheat_Sheet.html",
    "https://www.rfc-editor.org/rfc/rfc9525.html",
    "https://pkg.go.dev/crypto/tls#Config",
    "https://codeql.github.com/codeql-query-help/python/py-request-without-cert-validation/",
    "https://docs.python.org/3/library/ssl.html"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://top10.owasp.org/2025/A04_2025-Cryptographic_Failures",
      "https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Security_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "ietf",
     "name": "IETF",
     "sources": [
      "https://www.rfc-editor.org/rfc/rfc9525.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://pkg.go.dev/crypto/tls#Config"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://codeql.github.com/codeql-query-help/python/py-request-without-cert-validation/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://docs.python.org/3/library/ssl.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-tls-verify-on",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-variants-as-tagged-union",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "types",
    "modelling"
   ],
   "lang": "core",
   "applies_when": "A type or function models a value that is one of several variants or states.",
   "context": [],
   "tool_gate": "A type checker with exhaustiveness checking (`never` in TypeScript, `assert_never` in Python).",
   "note": "one vendor's engineering guidance (opinion), from pstack principle-model-the-domain and typescript-best-practices",
   "lacks": null,
   "rule": "Model variants as a tagged union, and make every switch over it fail the build when a variant is unhandled.",
   "why": "A bag of optional fields or booleans that must stay in sync allows states that mean nothing, and each reader has to know which combinations are legal. A new variant then slips past every switch that lacks an exhaustiveness check.",
   "counterpoint": "\"Two booleans are simpler.\" They are until a third state appears or the booleans disagree. For a true on/off flag with no related fields, a boolean is fine.",
   "fail": {
    "language": "ts",
    "code": "type Job = { running: boolean; failed: boolean; error?: string; result?: Result }"
   },
   "pass": {
    "language": "ts",
    "code": "type Job =\n  | { kind: \"running\" }\n  | { kind: \"failed\"; error: string }\n  | { kind: \"done\"; result: Result }\n\nfunction label(job: Job): string {\n  switch (job.kind) {\n    case \"running\": return \"running\"\n    case \"failed\": return job.error\n    case \"done\": return \"done\"\n    default: { const _exhaustive: never = job; throw new Error(\"unhandled\") }\n  }\n}"
   },
   "sources": [
    "https://github.com/cursor/plugins/blob/main/pstack/skills/principle-model-the-domain/SKILL.md",
    "https://github.com/cursor/plugins/blob/main/pstack/skills/typescript-best-practices/SKILL.md",
    "https://www.typescriptlang.org/docs/handbook/2/narrowing.html",
    "https://doc.rust-lang.org/book/ch06-02-match.html"
   ],
   "orgs": [
    {
     "id": "cursor",
     "name": "Cursor",
     "sources": [
      "https://github.com/cursor/plugins/blob/main/pstack/skills/principle-model-the-domain/SKILL.md",
      "https://github.com/cursor/plugins/blob/main/pstack/skills/typescript-best-practices/SKILL.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://www.typescriptlang.org/docs/handbook/2/narrowing.html"
     ],
     "strength": "supports"
    },
    {
     "id": "rust",
     "name": "Rust project",
     "sources": [
      "https://doc.rust-lang.org/book/ch06-02-match.html"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/function#fn-variants-as-tagged-union",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "fn-xml-no-external-entities",
   "level": "function",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "xml",
    "parsing"
   ],
   "lang": "core",
   "applies_when": "Code parses XML, SVG, SOAP, SAML, or an office document that a user or another system can supply.",
   "context": [],
   "tool_gate": "CodeQL `py/xxe`; Bandit B313 to B319 (stdlib XML parse calls) and B405 to B409 (XML imports).",
   "note": "ASVS 1.5.1; sys-ssrf covers outbound fetches that XXE can also trigger",
   "lacks": null,
   "rule": "Parse untrusted XML with external entity resolution turned off, for example through a hardened parser such as `defusedxml`.",
   "why": "A DTD can declare an entity whose value is `file:///etc/passwd` or an internal URL, and a parser that resolves it pastes that content into the document or makes the request for the attacker. Nested internal entities can also expand a small file into gigabytes. Where the parser allows it, also turn off DTDs and the parser's network access. Each parser has its own switches, so name them in the code instead of trusting the library default.",
   "counterpoint": "\"Recent Python XML modules already refuse external entities.\" Some do. The FAIL above opts back in, Java and .NET parsers have their own defaults, and a later change to the parser config reopens the hole. An explicit hardened parser makes the setting visible in review.",
   "fail": {
    "language": "python",
    "code": "parser = lxml.etree.XMLParser(resolve_entities=True, no_network=False)\ndoc = lxml.etree.fromstring(request.get_data(), parser)"
   },
   "pass": {
    "language": "python",
    "code": "import defusedxml.ElementTree as ET\ndoc = ET.fromstring(request.get_data())   # rejects DTD entities by default"
   },
   "sources": [
    "https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html",
    "https://cwe.mitre.org/data/definitions/611.html",
    "https://portswigger.net/web-security/xxe",
    "https://codeql.github.com/codeql-query-help/python/py-xxe/"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mitre",
     "name": "MITRE",
     "sources": [
      "https://cwe.mitre.org/data/definitions/611.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "portswigger",
     "name": "PortSwigger",
     "sources": [
      "https://portswigger.net/web-security/xxe"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://codeql.github.com/codeql-query-help/python/py-xxe/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/function#fn-xml-no-external-entities",
   "markdown_url": "https://goodcodereview.com/function.md"
  },
  {
   "id": "inc-perf-fix-has-profile-evidence",
   "level": "incident",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "performance",
    "testing"
   ],
   "topics": [
    "verification"
   ],
   "lang": "core",
   "applies_when": "An incident or a slowdown is closed by a code or config change meant to make it faster.",
   "context": [],
   "tool_gate": null,
   "note": "Rule backed by Google (Go blog, SRE troubleshooting), Brendan Gregg (USE, flame graphs), GitLab, and Microsoft; \"the same measurement after the fix\" has 2 orgs (Google, GitLab) and sits in Why",
   "lacks": null,
   "rule": "Tie the performance fix to evidence that names the bottleneck: a profile, a trace, or a USE check.",
   "why": "A guessed fix often speeds up code that was not slow, and the incident comes back. The profile that found the bottleneck is also the cheapest proof that the fix removed it, so take the same measurement again after the fix, under the same load ([[chg-perf-claim-evidence]]).",
   "counterpoint": "\"In an outage we mitigate first.\" Yes. Rolling back or adding capacity to stop the bleeding needs no profile. The rule covers the fix that closes the incident, which comes after the system is stable. Capacity kept as that fix, with no profile behind it, hides a code problem rather than removing it.",
   "fail": {
    "language": "text",
    "code": "postmortem: \"latency spike; increased pod memory and added a cache\"\nno profile, no trace, nothing that names where the time went"
   },
   "pass": {
    "language": "text",
    "code": "CPU profile: 61% in json.Marshal on /export (flame graph linked)\nfix: stream the response; after: p99 1.9 s -> 240 ms, same load replay"
   },
   "sources": [
    "https://go.dev/blog/pprof",
    "https://sre.google/sre-book/effective-troubleshooting/",
    "https://www.brendangregg.com/usemethod.html",
    "https://www.brendangregg.com/flamegraphs.html",
    "https://docs.gitlab.com/development/performance/",
    "https://learn.microsoft.com/en-us/azure/well-architected/performance-efficiency/optimize-code-infrastructure"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://go.dev/blog/pprof",
      "https://sre.google/sre-book/effective-troubleshooting/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://docs.gitlab.com/development/performance/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/well-architected/performance-efficiency/optimize-code-infrastructure"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "brendan-gregg",
     "name": "Brendan Gregg",
     "sources": [
      "https://www.brendangregg.com/usemethod.html",
      "https://www.brendangregg.com/flamegraphs.html"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/incident#inc-perf-fix-has-profile-evidence",
   "markdown_url": "https://goodcodereview.com/incident.md"
  },
  {
   "id": "inc-rollback-path-tested",
   "level": "incident",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "reliability"
   ],
   "topics": [
    "incident",
    "rollback"
   ],
   "lang": "core",
   "applies_when": "A runbook or incident response relies on rollback, failover, or a manual mitigation.",
   "context": [],
   "tool_gate": null,
   "note": "exercising runbook mitigations is backed by Dropbox (monthly failovers), Slack (a restore runbook failed until drilled; use rollback tooling often), Cloudflare (regular cut tests; a kill switch SOP failed on a rule type it had never met) and Shopify (repeated Game Days); naming who may trigger the mitigation has no source and sits in Why",
   "lacks": null,
   "rule": "Exercise each mitigation that a runbook names (rollback, failover, flag off) on a regular schedule.",
   "why": "Rollbacks fail on schema changes or removed artifacts, backups stop running without anyone noticing, and untested failover paths rot. People who rarely use a rollback tool hesitate to use it in an incident. The runbook should also say who may trigger each mitigation, so nobody waits for permission during the outage.",
   "counterpoint": "\"Drills are costly.\" Start with the mitigations an incident would need first, and drill again when a migration or release process changes. A drill is cheaper than finding out during an outage that the rollback is impossible.",
   "fail": {
    "language": "text",
    "code": "Runbook: \"roll back to previous version\" (last attempted: never; migration not reversible)"
   },
   "pass": {
    "language": "text",
    "code": "Runbook: rollback drilled monthly, last run 2026-10-01; schema is expand-only; on-call may trigger."
   },
   "sources": [
    "https://sre.google/workbook/canarying-releases/",
    "https://aws.amazon.com/builders-library/implementing-health-checks/",
    "https://dropbox.tech/infrastructure/disaster-readiness-test-failover-blackhole-sjc",
    "https://slack.engineering/break-stuff-on-purpose/",
    "https://slack.engineering/deploy-safety/",
    "https://blog.cloudflare.com/5-december-2025-outage/",
    "https://blog.cloudflare.com/cloudflare-incident-on-november-14-2024-resulting-in-lost-logs/",
    "https://shopify.engineering/four-steps-creating-effective-game-day-tests"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://sre.google/workbook/canarying-releases/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://aws.amazon.com/builders-library/implementing-health-checks/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "dropbox",
     "name": "Dropbox",
     "sources": [
      "https://dropbox.tech/infrastructure/disaster-readiness-test-failover-blackhole-sjc"
     ],
     "strength": "unjudged"
    },
    {
     "id": "slack",
     "name": "Slack",
     "sources": [
      "https://slack.engineering/break-stuff-on-purpose/",
      "https://slack.engineering/deploy-safety/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cloudflare",
     "name": "Cloudflare",
     "sources": [
      "https://blog.cloudflare.com/5-december-2025-outage/",
      "https://blog.cloudflare.com/cloudflare-incident-on-november-14-2024-resulting-in-lost-logs/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "shopify",
     "name": "Shopify",
     "sources": [
      "https://shopify.engineering/four-steps-creating-effective-game-day-tests"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/incident#inc-rollback-path-tested",
   "markdown_url": "https://goodcodereview.com/incident.md"
  },
  {
   "id": "mod-behaviour-lives-with-state",
   "level": "module",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "design",
    "oop"
   ],
   "lang": "core",
   "applies_when": "A function computes something only from one object's own fields.",
   "context": [],
   "tool_gate": null,
   "note": "Microsoft (entity methods own the invariants), PMD (DataClass, data-behaviour proximity) and DevIQ (anemic model) put the rule on the object; Microsoft and DevIQ also give the CRUD and functional exceptions",
   "lacks": null,
   "rule": "Put the invariants and calculations that read only one object's fields on that object.",
   "why": "A rule about one object scattered across services makes every caller learn where the rule lives, and the object cannot enforce it. Microsoft says an entity's methods \"take care of the invariants and rules of the entity instead of having those rules spread across the application layer\". PMD calls a data holder whose behaviour lives elsewhere a sign of \"poor data-behaviour proximity\". DevIQ says model objects should \"use encapsulation to manage their internal state and behavior\".",
   "counterpoint": "\"Records, data classes and dataclasses are made to carry data.\" They are, and a carrier with no rules has nothing to move. The rule is about the calculation, not the declaration, so `total()` on a record does not stop it being a record. Keep the type a pure carrier when it holds only data. A simple CRUD context can keep a data-only model, and a functional design keeps behaviour in functions over data. The rule is for an object model.",
   "fail": {
    "language": "text",
    "code": "def order_total(order): return sum(l.price * l.qty for l in order.lines)"
   },
   "pass": {
    "language": "text",
    "code": "class Order:\n    def total(self): return sum(l.price * l.qty for l in self.lines)"
   },
   "sources": [
    "https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/microservice-domain-model",
    "https://docs.pmd-code.org/latest/pmd_rules_java_design.html",
    "https://deviq.com/domain-driven-design/anemic-model/"
   ],
   "orgs": [
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/microservice-domain-model"
     ],
     "strength": "unjudged"
    },
    {
     "id": "pmd",
     "name": "PMD",
     "sources": [
      "https://docs.pmd-code.org/latest/pmd_rules_java_design.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nimblepros",
     "name": "NimblePros (DevIQ)",
     "sources": [
      "https://deviq.com/domain-driven-design/anemic-model/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/module#mod-behaviour-lives-with-state",
   "markdown_url": "https://goodcodereview.com/module.md"
  },
  {
   "id": "mod-bounded-fan-out",
   "level": "module",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "design",
    "structure"
   ],
   "lang": "core",
   "applies_when": "A change adds imports or type references to a class, file or function that already depends on many others.",
   "context": [],
   "tool_gate": "Microsoft CA1506 (95 types, 40 members), Checkstyle ClassFanOutComplexity (20), eslint-plugin-import import/max-dependencies (10), PMD CouplingBetweenObjects (20) and ExcessiveImports (30).",
   "note": "a documented limit on how many other types or modules a unit depends on - Microsoft (CA1506), Checkstyle, eslint-plugin-import, PMD; the defaults differ by a factor of ten, so the Rule asks for a limit and does not name a number",
   "lacks": null,
   "rule": "Set a limit on how many other types or modules one unit depends on, and split a unit that passes it.",
   "why": "A unit that depends on many others breaks when any of them changes and is hard to test, because each dependency needs a stand-in. eslint-plugin-import says \"a module with too many dependencies is a code smell, and usually indicates the module is doing too much\". Microsoft's rule gives the same reason: high class coupling is \"difficult to maintain\".",
   "counterpoint": "\"The entry point has to import everything to wire it up.\" It does, so allowlist the composition root and nothing else. Microsoft's own page says to suppress the rule when the type \"is considered maintainable despite its large number of dependencies\".",
   "fail": {
    "language": "text",
    "code": "order_service.py: 46 imports (payments, shipping, tax, email, pdf, audit, 40 more)"
   },
   "pass": {
    "language": "text",
    "code": "order_service.py: 9 imports; tax, shipping and notification each sit behind one module\neslint: \"import/max-dependencies\": [\"error\", { \"max\": 10 }]"
   },
   "sources": [
    "https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1506",
    "https://checkstyle.sourceforge.io/checks/metrics/classfanoutcomplexity.html",
    "https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/max-dependencies.md",
    "https://pmd.github.io/pmd/pmd_rules_java_design.html"
   ],
   "orgs": [
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1506"
     ],
     "strength": "unjudged"
    },
    {
     "id": "checkstyle",
     "name": "Checkstyle",
     "sources": [
      "https://checkstyle.sourceforge.io/checks/metrics/classfanoutcomplexity.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "importjs",
     "name": "eslint-plugin-import",
     "sources": [
      "https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/max-dependencies.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "pmd",
     "name": "PMD",
     "sources": [
      "https://pmd.github.io/pmd/pmd_rules_java_design.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/module#mod-bounded-fan-out",
   "markdown_url": "https://goodcodereview.com/module.md"
  },
  {
   "id": "mod-class-size-budget",
   "level": "module",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "design",
    "size"
   ],
   "lang": "core",
   "applies_when": "One class or type passes the project's size limit for fields, public methods or lines, or the project sets no limit. A long file that holds several units belongs to code-file-lines-ceiling, and a module that mixes concepts is judged for cohesion, not size.",
   "context": [],
   "tool_gate": "Pylint too-many-instance-attributes (R0902, max-attributes default 7) and too-many-public-methods (R0904, default 20), PMD NcssCount (classReportLevel default 1500) or GodClass, RuboCop Metrics/ClassLength (default 100).",
   "note": "Pylint, PMD and RuboCop enforce a class-size limit but measure different things (fields, public methods, lines, NCSS); Pylint and PMD also give the rationale (a simpler class; split a god class). Microsoft CA1505 is a maintainability index, not a size gate, so it is not listed. The card asks for the project's limit and treats the result as a prompt to ask what the class owns",
   "lacks": null,
   "rule": "Split a class that passes the project's size limit for fields, public methods or lines, unless it is a data record.",
   "why": "A class that owns too much has too many reasons to change, and the project's linter should carry a limit for it. Pricing, tax, invoicing, dunning and notification in one type means five sets of fields, five test setups and a constructor nobody can call correctly. The linters count different things: fields, public methods, lines, source statements or a maintainability index. Any of them is a prompt to ask what the class owns. A data record with many fields is a different case, and a wide but cohesive API can be justified.",
   "counterpoint": "\"Splitting a large class leaves shallow pieces that call each other.\" That happens when the split follows methods instead of knowledge. Move the state with the methods that use it, and keep one thin entry point for callers. If the pieces would only forward, the class is not too large. It is one concept with a wide surface, and the limit is the wrong signal. A data record is judged by its shape, not by this card.",
   "fail": {
    "language": "text",
    "code": "class BillingService:        # 25 fields, 40 public methods\n    price(); tax(); invoice(); dunning(); notify()"
   },
   "pass": {
    "language": "text",
    "code": "class Pricing: ...\nclass Tax: ...\nclass Invoice: ...\nclass Dunning: ...\nclass Notifier: ...\n# each owns its state and stays under the project's limit"
   },
   "sources": [
    "https://pylint.readthedocs.io/en/stable/user_guide/configuration/all-options.html",
    "https://pylint.readthedocs.io/en/stable/user_guide/messages/refactor/too-many-instance-attributes.html",
    "https://pmd.github.io/pmd/pmd_rules_java_design.html",
    "https://docs.rubocop.org/rubocop/latest/cops_metrics.html"
   ],
   "orgs": [
    {
     "id": "pylint",
     "name": "Pylint",
     "sources": [
      "https://pylint.readthedocs.io/en/stable/user_guide/configuration/all-options.html",
      "https://pylint.readthedocs.io/en/stable/user_guide/messages/refactor/too-many-instance-attributes.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "pmd",
     "name": "PMD",
     "sources": [
      "https://pmd.github.io/pmd/pmd_rules_java_design.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "rubocop",
     "name": "RuboCop",
     "sources": [
      "https://docs.rubocop.org/rubocop/latest/cops_metrics.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/module#mod-class-size-budget",
   "markdown_url": "https://goodcodereview.com/module.md"
  },
  {
   "id": "mod-colour-tokens-meet-contrast",
   "level": "module",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "accessibility",
    "usability"
   ],
   "topics": [
    "frontend",
    "css",
    "design-system"
   ],
   "lang": "core",
   "applies_when": "A design-system module or theme defines foreground and background colour tokens.",
   "context": [
    "ui"
   ],
   "tool_gate": "axe `color-contrast` on rendered components; a unit test that computes the ratio for each token pair. axe cannot see text on images or gradients.",
   "note": "3:1 for non-text parts (borders, icons, focus): A11y Project, web.dev, WebAIM. WebAIM's checklist restates WCAG 1.4.11; SC 1.4.11 was not read as its own page",
   "lacks": null,
   "rule": "Make every text and background token pair meet 4.5:1 (3:1 for large text), and every border, icon, and focus colour meet 3:1 against its neighbour.",
   "why": "Low contrast is invisible to the author on a bright monitor. Fixing it once in the tokens fixes every component. Fixing it per component means hunting through each one, and each new component can reintroduce it.",
   "counterpoint": "\"The brand colour fails.\" A logo is exempt. A brand-coloured button label or link is not. Darken the shade used for text and keep the brand shade for fills. The ratio is exact: 4.499:1 fails.",
   "fail": {
    "language": "css",
    "code": ":root {\n  --text-muted: #9aa0a6;     /* 2.6:1 on white */\n  --bg: #ffffff;\n  --input-border: #e0e0e0;   /* 1.3:1 on white */\n}"
   },
   "pass": {
    "language": "css",
    "code": ":root {\n  --text-muted: #5f6368;     /* 6.0:1 on white */\n  --bg: #ffffff;\n  --input-border: #767676;   /* 4.5:1 on white, above the 3:1 floor */\n}"
   },
   "sources": [
    "https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html",
    "https://dequeuniversity.com/rules/axe/html/4.10/color-contrast",
    "https://www.a11yproject.com/checklist/",
    "https://developer.apple.com/tutorials/data/design/human-interface-guidelines/accessibility.json",
    "https://web.dev/learn/accessibility/focus",
    "https://www.w3.org/WAI/test-evaluate/preliminary/",
    "https://webaim.org/standards/wcag/checklist"
   ],
   "orgs": [
    {
     "id": "w3c",
     "name": "W3C",
     "sources": [
      "https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html",
      "https://www.w3.org/WAI/test-evaluate/preliminary/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "deque",
     "name": "Deque",
     "sources": [
      "https://dequeuniversity.com/rules/axe/html/4.10/color-contrast"
     ],
     "strength": "unjudged"
    },
    {
     "id": "a11yproject",
     "name": "The A11y Project",
     "sources": [
      "https://www.a11yproject.com/checklist/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "apple",
     "name": "Apple",
     "sources": [
      "https://developer.apple.com/tutorials/data/design/human-interface-guidelines/accessibility.json"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://web.dev/learn/accessibility/focus"
     ],
     "strength": "unjudged"
    },
    {
     "id": "webaim",
     "name": "WebAIM",
     "sources": [
      "https://webaim.org/standards/wcag/checklist"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/module#mod-colour-tokens-meet-contrast",
   "markdown_url": "https://goodcodereview.com/module.md"
  },
  {
   "id": "mod-deep-modules",
   "level": "module",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "design"
   ],
   "lang": "core",
   "applies_when": "A new class or module is introduced, or an existing interface grows.",
   "context": [],
   "tool_gate": null,
   "note": "a small interface, nothing more than callers need - Microsoft Framework Design Guidelines (\"no more and no fewer\" members), Rust API Guidelines (hide the representation, \"promise less to the client\"), Go review comments (do not define an interface before it is used); the \"interface as complex as the implementation\" test is Ousterhout alone and sits in the Why",
   "lacks": null,
   "rule": "Prefer a small interface that hides its implementation, and add a member only when callers need it.",
   "why": "An interface with too many members is hard to learn and hard to implement, and a public detail is a commitment you cannot change later. A shallow module, whose interface is nearly as complex as its implementation, charges the reader the cost of learning it and returns nothing. The complexity moves; it does not shrink.",
   "counterpoint": "Ousterhout and Martin disagree. Martin favours many very small functions, and Ousterhout says that yields shallow, entangled pieces. Evidence is thin on both sides. Ask the local question: does the split help a reader here?",
   "fail": {
    "language": "text",
    "code": "class Reader:\n    def open(self); def read_line(self); def set_encoding(self, e); def close(self)\n    # caller must sequence all four; same work in a function would be one call"
   },
   "pass": {
    "language": "text",
    "code": "def read_lines(path, encoding=\"utf-8\") -> list[str]: ..."
   },
   "sources": [
    "https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/abstractions-abstract-types-and-interfaces",
    "https://rust-lang.github.io/api-guidelines/future-proofing.html",
    "https://go.dev/wiki/CodeReviewComments",
    "https://dev.to/sportebois/software-design-red-flags-wisdom-nuggets-from-john-ousterhout-43i2",
    "https://github.com/johnousterhout/aposd-vs-clean-code/blob/main/README.md"
   ],
   "orgs": [
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/abstractions-abstract-types-and-interfaces"
     ],
     "strength": "unjudged"
    },
    {
     "id": "rust",
     "name": "Rust project",
     "sources": [
      "https://rust-lang.github.io/api-guidelines/future-proofing.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://go.dev/wiki/CodeReviewComments"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "portebois",
     "name": "Sébastien Portebois",
     "sources": [
      "https://dev.to/sportebois/software-design-red-flags-wisdom-nuggets-from-john-ousterhout-43i2"
     ],
     "strength": "unjudged"
    },
    {
     "id": "ousterhout",
     "name": "John Ousterhout",
     "sources": [
      "https://github.com/johnousterhout/aposd-vs-clean-code/blob/main/README.md"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/module#mod-deep-modules",
   "markdown_url": "https://goodcodereview.com/module.md"
  },
  {
   "id": "mod-derive-dont-duplicate",
   "level": "module",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "compatibility",
    "maintainability"
   ],
   "topics": [
    "duplication",
    "schemas"
   ],
   "lang": "core",
   "applies_when": "A schema, spec, enum or list owns a shape that other code restates by hand.",
   "context": [],
   "tool_gate": "Codegen from the schema, or a test that diffs the two lists.",
   "note": "Uber, Stripe and Spotify back the Rule without cursor/plugins",
   "lacks": null,
   "rule": "Derive a type, list or enum from the schema or list that owns it, or add a build check that fails when the two disagree.",
   "why": "A hand-kept copy drifts. The next field added to the schema is missing from the copy, and nothing fails until a user hits it.",
   "counterpoint": "\"Generated code is awkward to read.\" A parity test gives the same safety with hand-written types. The rule fails only when neither derivation nor a check exists.",
   "fail": {
    "language": "text",
    "code": "openapi.yaml defines Status: [open, closed, void]\nstatus.py:   class Status(Enum): OPEN, CLOSED     # void missing"
   },
   "pass": {
    "language": "text",
    "code": "status.py is generated from openapi.yaml in the build, or a test asserts the two sets are equal"
   },
   "sources": [
    "https://github.com/cursor/plugins/blob/main/pstack/skills/principle-type-system-discipline/SKILL.md",
    "https://github.com/cursor/plugins/blob/main/pstack/skills/architect/references/design-red-flags.md",
    "https://www.uber.com/blog/designing-mcp-gateway/",
    "https://www.uber.com/blog/high-performance-grpc/",
    "https://stripe.dev/blog/how-api-changes-flow-into-stripes-developer-products",
    "https://stripe.dev/blog/shepherd-how-stripe-adapted-chronon-to-scale-ml-feature-development",
    "https://engineering.atspotify.com/2026/5/spotify-ads-api-claude-plugins"
   ],
   "orgs": [
    {
     "id": "cursor",
     "name": "Cursor",
     "sources": [
      "https://github.com/cursor/plugins/blob/main/pstack/skills/principle-type-system-discipline/SKILL.md",
      "https://github.com/cursor/plugins/blob/main/pstack/skills/architect/references/design-red-flags.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "uber",
     "name": "Uber",
     "sources": [
      "https://www.uber.com/blog/designing-mcp-gateway/",
      "https://www.uber.com/blog/high-performance-grpc/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "stripe",
     "name": "Stripe",
     "sources": [
      "https://stripe.dev/blog/how-api-changes-flow-into-stripes-developer-products",
      "https://stripe.dev/blog/shepherd-how-stripe-adapted-chronon-to-scale-ml-feature-development"
     ],
     "strength": "unjudged"
    },
    {
     "id": "spotify",
     "name": "Spotify",
     "sources": [
      "https://engineering.atspotify.com/2026/5/spotify-ads-api-claude-plugins"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/module#mod-derive-dont-duplicate",
   "markdown_url": "https://goodcodereview.com/module.md"
  },
  {
   "id": "mod-fowler-smells",
   "level": "module",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "design",
    "refactoring"
   ],
   "lang": "core",
   "applies_when": "A reviewer sees long methods, feature envy, data clumps, primitive obsession, speculative generality, or divergent change.",
   "context": [],
   "tool_gate": null,
   "note": "name the issue and give a reason, not taste - Google (\"Aspects of software design are almost never a pure style issue\"), GitLab (\"Be explicit\", \"Offer alternative implementations\"), Palantir (\"Suggestion: extract method to improve legibility\", \"Critique the code, not the author\"); the smell catalog itself comes from Fowler and Beck (persons) through Ousterhout's red flags",
   "lacks": null,
   "rule": "Name the smell, point to the line, and suggest the refactoring; do not file smells as taste.",
   "why": "Smells are cheap to see and expensive to ignore, but a vague \"this feels off\" gives the author nothing to fix.",
   "counterpoint": "\"Smells are subjective.\" Yes: they signal; they do not prove. That is why the comment names the smell, the line, and the refactoring. \"This is messy\" gives the author nothing to act on.",
   "fail": {
    "language": "text",
    "code": "review: \"This is messy.\""
   },
   "pass": {
    "language": "text",
    "code": "review: \"should-fix: price_total reads only Order fields (feature envy). Move it to Order.\""
   },
   "sources": [
    "https://dev.to/sportebois/software-design-red-flags-wisdom-nuggets-from-john-ousterhout-43i2",
    "https://google.github.io/eng-practices/review/reviewer/looking-for.html",
    "https://google.github.io/eng-practices/review/reviewer/standard.html",
    "https://docs.gitlab.com/development/code_review/",
    "https://blog.palantir.com/code-review-best-practices-19e02780015f"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://google.github.io/eng-practices/review/reviewer/looking-for.html",
      "https://google.github.io/eng-practices/review/reviewer/standard.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://docs.gitlab.com/development/code_review/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "palantir",
     "name": "Palantir",
     "sources": [
      "https://blog.palantir.com/code-review-best-practices-19e02780015f"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "portebois",
     "name": "Sébastien Portebois",
     "sources": [
      "https://dev.to/sportebois/software-design-red-flags-wisdom-nuggets-from-john-ousterhout-43i2"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/module#mod-fowler-smells",
   "markdown_url": "https://goodcodereview.com/module.md"
  },
  {
   "id": "mod-inject-boundary-collaborators",
   "level": "module",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "testing",
    "maintainability"
   ],
   "topics": [
    "design"
   ],
   "lang": "core",
   "applies_when": "A class uses a database, HTTP, cloud or clock collaborator.",
   "context": [],
   "tool_gate": null,
   "note": "from the author's own Python review guide",
   "lacks": null,
   "rule": "Inject boundary collaborators through the constructor; do not construct them inside or reach for a global.",
   "why": "Hard-wired collaborators force tests to patch module internals and tie the class to one environment.",
   "counterpoint": "\"Injection everywhere is enterprise bloat.\" Inject only the boundary (DB, HTTP, cloud, clock). Internal collaborators stay plain and real in tests.",
   "fail": {
    "language": "text",
    "code": "class Billing:\n    def charge(self, o): return StripeClient(KEY).charge(o)"
   },
   "pass": {
    "language": "text",
    "code": "class Billing:\n    def __init__(self, gateway): self._gateway = gateway"
   },
   "sources": [
    "https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines",
    "https://symfony.com/doc/current/service_container.html",
    "https://testing.googleblog.com/2008/08/by-miko-hevery-so-you-decided-to.html"
   ],
   "orgs": [
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines"
     ],
     "strength": "supports"
    },
    {
     "id": "symfony",
     "name": "Symfony",
     "sources": [
      "https://symfony.com/doc/current/service_container.html"
     ],
     "strength": "supports"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://testing.googleblog.com/2008/08/by-miko-hevery-so-you-decided-to.html"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "strong",
   "url": "https://goodcodereview.com/module#mod-inject-boundary-collaborators",
   "markdown_url": "https://goodcodereview.com/module.md"
  },
  {
   "id": "mod-internals-unreachable",
   "level": "module",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "encapsulation",
    "boundaries"
   ],
   "lang": "core",
   "applies_when": "A module has an intended public surface, with internal files, symbols or members.",
   "context": [],
   "tool_gate": "Import-boundary lint (import-linter, ESLint `no-restricted-imports`, package `exports`), Pylint protected-access (W0212).",
   "note": "the import half has Google (Go internal packages and Bazel visibility), Cursor's design red flags and Microsoft (the `internal` keyword, a definition); the member half has Refactoring.Guru, PEP 8 and Pylint. A test may use a documented test-only export",
   "lacks": null,
   "rule": "Keep a module's internals unimportable and untouched from outside it. A caller uses the public surface, not a private file or member.",
   "why": "Anything a caller can import becomes the interface, and a field a caller writes becomes part of the contract. A naming convention such as a leading underscore stops no one, and the module can no longer change its internals without breaking callers.",
   "counterpoint": "\"A boundary check is overhead for a small repo, and tests need internals.\" One lint contract is cheap, and the first outside import of an internal costs more. Tests may use a documented test-only export, and Java's qualified `exports ... to` or C#'s `InternalsVisibleTo` give them a door. For a single-file module, there is no inside to protect, so mark `N/A`.",
   "fail": {
    "language": "python",
    "code": "from billing.internal.tax_tables import RATES   # nothing stops this\norder._lines.append(line)                       # outside Order"
   },
   "pass": {
    "language": "text",
    "code": "billing/__init__.py exports calculate_tax; import-linter contract forbids billing.internal.* from outside billing\norder.add_line(line)"
   },
   "sources": [
    "https://github.com/cursor/plugins/blob/main/pstack/skills/architect/references/design-red-flags.md",
    "https://bazel.build/concepts/visibility",
    "https://pkg.go.dev/cmd/go#hdr-Internal_packages",
    "https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/internal",
    "https://refactoring.guru/smells/inappropriate-intimacy",
    "https://peps.python.org/pep-0008/",
    "https://pylint.readthedocs.io/en/latest/user_guide/messages/warning/protected-access.html"
   ],
   "orgs": [
    {
     "id": "cursor",
     "name": "Cursor",
     "sources": [
      "https://github.com/cursor/plugins/blob/main/pstack/skills/architect/references/design-red-flags.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://bazel.build/concepts/visibility",
      "https://pkg.go.dev/cmd/go#hdr-Internal_packages"
     ],
     "strength": "supports"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/internal"
     ],
     "strength": "supports"
    },
    {
     "id": "refactoring-guru",
     "name": "Refactoring.Guru",
     "sources": [
      "https://refactoring.guru/smells/inappropriate-intimacy"
     ],
     "strength": "unjudged"
    },
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://peps.python.org/pep-0008/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "pylint",
     "name": "Pylint",
     "sources": [
      "https://pylint.readthedocs.io/en/latest/user_guide/messages/warning/protected-access.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/module#mod-internals-unreachable",
   "markdown_url": "https://goodcodereview.com/module.md"
  },
  {
   "id": "mod-law-of-demeter",
   "level": "module",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "design",
    "coupling"
   ],
   "lang": "core",
   "applies_when": "A change adds a call chain that reaches through one object to use another, such as order.customer().address().city().",
   "context": [],
   "tool_gate": "PMD LawOfDemeter (trustRadius, default 1), JetBrains inspection LawOfDemeter.",
   "note": "PMD, Google's Testing Blog (a heading only) and JetBrains state the rule. PMD's trustRadius (default 1) and JetBrains' LawOfDemeter inspection gate it with the same threshold: one call on an object another call returned; JetBrains notes the wrapper-method cost and calls the law a style guideline",
   "lacks": null,
   "rule": "Call methods on your direct collaborators, and do not call a method on an object that another call returned (a chain of two or more getters).",
   "why": "A chain such as `order.customer().address().city()` ties the caller to the structure of three classes it does not own, so a change in any of them breaks it. PMD describes the rule as forbidding \"fetching data from 'too far away'\" to reduce coupling.",
   "counterpoint": "\"Fluent builders and stream pipelines chain by design.\" They return the same type or a value, so they do not expose structure. The rule targets getters that expose another object's parts. Every wrapper is code you now own. Pay that cost on the path that changes, and leave a stable library chain alone. The inspection ignores library calls by default. The law is a style guideline, which is why the severity is `discuss`. Use the project's tool setting as the threshold.",
   "fail": {
    "language": "text",
    "code": "city = order.customer().address().city()"
   },
   "pass": {
    "language": "text",
    "code": "city = order.shipping_city()      # Order owns how it finds the city"
   },
   "sources": [
    "https://pmd.github.io/pmd/pmd_rules_java_design.html#lawofdemeter",
    "https://testing.googleblog.com/2008/08/by-miko-hevery-so-you-decided-to.html",
    "https://www.jetbrains.com/help/inspectopedia/LawOfDemeter.html"
   ],
   "orgs": [
    {
     "id": "pmd",
     "name": "PMD",
     "sources": [
      "https://pmd.github.io/pmd/pmd_rules_java_design.html#lawofdemeter"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://testing.googleblog.com/2008/08/by-miko-hevery-so-you-decided-to.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "jetbrains",
     "name": "JetBrains",
     "sources": [
      "https://www.jetbrains.com/help/inspectopedia/LawOfDemeter.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/module#mod-law-of-demeter",
   "markdown_url": "https://goodcodereview.com/module.md"
  },
  {
   "id": "mod-layers-point-inward",
   "level": "module",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "architecture"
   ],
   "lang": "core",
   "applies_when": "The codebase has separable transport, logic, and outbound-boundary code.",
   "context": [],
   "tool_gate": "import-linter or similar",
   "note": "from the author's own Python review guide",
   "lacks": null,
   "rule": "Make dependencies point inward: surface, then logic, then boundary.",
   "why": "Once transport types leak into logic, you cannot reuse or test the logic without the transport, and a change to the API shape ripples into the rules. Name the layers for what they do (surface, logic, boundary).",
   "counterpoint": "\"Three layers is ceremony for a CRUD app.\" For a ten-endpoint app, yes; fold them. The check applies once you have real logic. It asks only that dependencies not point outward, not that you build all three folders.",
   "fail": {
    "language": "text",
    "code": "service/orders.py:  def create(request: HttpRequest) -> HttpResponse\ncontroller/orders.py: calls repository directly"
   },
   "pass": {
    "language": "text",
    "code": "controller -> service -> repository      (imports only along this arrow)\nservice takes plain domain types"
   },
   "sources": [
    "https://learn.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/common-web-application-architectures",
    "https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/hexagonal-architecture.html",
    "https://netflixtechblog.com/ready-for-changes-with-hexagonal-architecture-b315ec967749"
   ],
   "orgs": [
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/common-web-application-architectures"
     ],
     "strength": "supports"
    },
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/hexagonal-architecture.html"
     ],
     "strength": "supports"
    },
    {
     "id": "netflix",
     "name": "Netflix",
     "sources": [
      "https://netflixtechblog.com/ready-for-changes-with-hexagonal-architecture-b315ec967749"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "strong",
   "url": "https://goodcodereview.com/module#mod-layers-point-inward",
   "markdown_url": "https://goodcodereview.com/module.md"
  },
  {
   "id": "mod-no-import-cycles",
   "level": "module",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "design",
    "structure"
   ],
   "lang": "core",
   "applies_when": "A change adds an import, dependency or project reference between modules, packages or build targets.",
   "context": [],
   "tool_gate": "eslint-plugin-import import/no-cycle, Pylint R0401 (cyclic-import), Nx @nx/enforce-module-boundaries, or ArchUnit slices().should().beFreeOfCycles().",
   "note": "the language or build tool rejects a cycle - Go (an import cycle is illegal), Bazel (targets form a directed acyclic graph), Cargo (the resolver does not allow cycles, except dev-dependencies); lint rules - eslint-plugin-import, Nx, Pylint, ArchUnit; scope to the package boundary, because Python and Rust allow cycles between modules",
   "lacks": null,
   "rule": "Keep the dependency graph between modules or packages acyclic, and break a cycle by moving the shared part into a third module that both depend on.",
   "why": "Two modules that import each other cannot be built, tested or understood alone, and a change in one can break the other. Go rejects the cycle at compile time, Bazel defines its dependency graph as acyclic, and Cargo tells authors to split a package so it \"remains strictly acyclic\". Where the language allows the cycle, it still ties the two modules into one.",
   "counterpoint": "\"Python lets you import inside the function, so the cycle works.\" That hides the cycle at run time and leaves the two modules coupled. Apply the rule at the package or module boundary, where the toolchain or a lint rule can check it, and allow a documented exception for generated code or a test-only dependency.",
   "fail": {
    "language": "text",
    "code": "billing/invoice.py   imports  customers/account.py\ncustomers/account.py imports  billing/invoice.py     # cycle"
   },
   "pass": {
    "language": "text",
    "code": "billing/invoice.py     imports  customers/account.py\ncustomers/account.py   imports  nothing from billing/\n(shared types live in customers/types.py, which both import)"
   },
   "sources": [
    "https://go.dev/ref/spec",
    "https://bazel.build/concepts/dependencies",
    "https://doc.rust-lang.org/cargo/reference/resolver.html",
    "https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-cycle.md",
    "https://nx.dev/docs/kb/resolve-circular-dependencies",
    "https://www.archunit.org/userguide/html/000_Index.html",
    "https://pylint.pycqa.org/en/stable/user_guide/messages/refactor/cyclic-import.html"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://go.dev/ref/spec",
      "https://bazel.build/concepts/dependencies"
     ],
     "strength": "unjudged"
    },
    {
     "id": "rust",
     "name": "Rust project",
     "sources": [
      "https://doc.rust-lang.org/cargo/reference/resolver.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "importjs",
     "name": "eslint-plugin-import",
     "sources": [
      "https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-cycle.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nx",
     "name": "Nx",
     "sources": [
      "https://nx.dev/docs/kb/resolve-circular-dependencies"
     ],
     "strength": "unjudged"
    },
    {
     "id": "archunit",
     "name": "ArchUnit",
     "sources": [
      "https://www.archunit.org/userguide/html/000_Index.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "pylint",
     "name": "Pylint",
     "sources": [
      "https://pylint.pycqa.org/en/stable/user_guide/messages/refactor/cyclic-import.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/module#mod-no-import-cycles",
   "markdown_url": "https://goodcodereview.com/module.md"
  },
  {
   "id": "mod-no-import-side-effects",
   "level": "module",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "testing",
    "maintainability"
   ],
   "topics": [
    "architecture"
   ],
   "lang": "core",
   "applies_when": "A module does anything at import or load time other than defining names.",
   "context": [],
   "tool_gate": null,
   "note": "from the author's own Python review guide",
   "lacks": null,
   "rule": "Keep I/O at the edges: importing a package must not touch the network, read files, or read the environment.",
   "why": "Import-time effects make tests slow and order-dependent, break tooling that merely imports the module, and fail in places that had no reason to need a network.",
   "counterpoint": "\"Loading once at import is fast.\" Load once at startup, explicitly, where the caller controls it and tests can substitute it.",
   "fail": {
    "language": "text",
    "code": "CLIENT = HttpClient(os.environ[\"API_URL\"])   # at import\nDATA = json.load(open(\"data.json\"))"
   },
   "pass": {
    "language": "text",
    "code": "def make_client(settings): return HttpClient(settings.api_url)\ndef load_data(path): ..."
   },
   "sources": [
    "https://airflow.apache.org/docs/apache-airflow/2.10.0/best-practices.html",
    "https://azure.github.io/azure-sdk/typescript_implementation.html",
    "https://dagster.io/blog/dignified-python-10-rules-to-improve-your-llm-agents",
    "https://raw.githubusercontent.com/UKGovernmentBEIS/inspect_evals/main/BEST_PRACTICES.md"
   ],
   "orgs": [
    {
     "id": "apache",
     "name": "Apache Software Foundation",
     "sources": [
      "https://airflow.apache.org/docs/apache-airflow/2.10.0/best-practices.html"
     ],
     "strength": "supports"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://azure.github.io/azure-sdk/typescript_implementation.html"
     ],
     "strength": "supports"
    },
    {
     "id": "dagster",
     "name": "Dagster",
     "sources": [
      "https://dagster.io/blog/dignified-python-10-rules-to-improve-your-llm-agents"
     ],
     "strength": "supports"
    },
    {
     "id": "aisi",
     "name": "UK AI Security Institute",
     "sources": [
      "https://raw.githubusercontent.com/UKGovernmentBEIS/inspect_evals/main/BEST_PRACTICES.md"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "strong",
   "url": "https://goodcodereview.com/module#mod-no-import-side-effects",
   "markdown_url": "https://goodcodereview.com/module.md"
  },
  {
   "id": "mod-no-logic-in-constructors",
   "level": "module",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "correctness",
    "testing"
   ],
   "topics": [
    "state"
   ],
   "lang": "core",
   "applies_when": "A constructor does work beyond assigning its arguments.",
   "context": [],
   "tool_gate": null,
   "note": "from the author's own Python review guide",
   "lacks": null,
   "rule": "Assign only in constructors, and put I/O, parsing, and heavy computation in a named factory.",
   "why": "A constructor with side effects makes every instantiation, including in tests, read a file or call an API. You need a real resource or a patch just to build the object.",
   "counterpoint": "\"A factory per class is ceremony.\" For a value object with no I/O, there is nothing to factor out. The rule targets constructors that read, call, or parse. With the factory, the test builds the object in one line.",
   "fail": {
    "language": "text",
    "code": "class PriceList:\n    def __init__(self, path):\n        self._prices = json.loads(path.read_text())"
   },
   "pass": {
    "language": "text",
    "code": "class PriceList:\n    def __init__(self, prices): self._prices = prices\n    @classmethod\n    def from_file(cls, path): ..."
   },
   "sources": [
    "https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/constructor",
    "https://google.github.io/styleguide/cppguide.html",
    "https://legacy.reactjs.org/docs/react-component.html"
   ],
   "orgs": [
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/constructor"
     ],
     "strength": "supports"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://google.github.io/styleguide/cppguide.html"
     ],
     "strength": "supports"
    },
    {
     "id": "meta",
     "name": "Meta",
     "sources": [
      "https://legacy.reactjs.org/docs/react-component.html"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "strong",
   "url": "https://goodcodereview.com/module#mod-no-logic-in-constructors",
   "markdown_url": "https://goodcodereview.com/module.md"
  },
  {
   "id": "mod-no-module-mutable-state",
   "level": "module",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "correctness",
    "testing"
   ],
   "topics": [
    "state"
   ],
   "lang": "core",
   "applies_when": "A module defines a value that changes after load: a module-level variable, a list, dict, counter, cache, client or singleton.",
   "context": [],
   "tool_gate": null,
   "note": "Google Python guide 2.5 allows a rare internal global behind functions; the Uber Go guide and Microsoft CA2211 (public static fields) say to avoid mutable global or static state. Only Google names class attributes, so that point sits in the Why",
   "lacks": null,
   "rule": "Keep no mutable state at module level; hold it on an instance.",
   "why": "Hidden global state leaks between tests, between requests, and between threads, and its order of mutation is unreadable. A mutable class or static field fails the same way, and the Google Python guide names class attributes as well. A frozen, load-once constant such as settings is the exception. A singleton used to dodge injection is not.",
   "counterpoint": "\"A module global is the simplest cache.\" It is also unbounded and shared by everything. If you need one, bound it (LRU or TTL), inject it, and tie its lifetime to what it caches. A rare, documented internal global behind functions can stay; say why in a comment.",
   "fail": {
    "language": "python",
    "code": "_cache = {}\n\ndef get(key):\n    if key not in _cache:\n        _cache[key] = load(key)\n    return _cache[key]"
   },
   "pass": {
    "language": "python",
    "code": "class Loader:\n    def __init__(self, cache):\n        self._cache = cache\n\n    def get(self, key):\n        if key not in self._cache:\n            self._cache[key] = load(key)\n        return self._cache[key]"
   },
   "sources": [
    "https://google.github.io/styleguide/pyguide.html#25-mutable-global-state",
    "https://github.com/uber-go/guide/blob/master/style.md#avoid-mutable-globals",
    "https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2211"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://google.github.io/styleguide/pyguide.html#25-mutable-global-state"
     ],
     "strength": "unjudged"
    },
    {
     "id": "uber",
     "name": "Uber",
     "sources": [
      "https://github.com/uber-go/guide/blob/master/style.md#avoid-mutable-globals"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2211"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/module#mod-no-module-mutable-state",
   "markdown_url": "https://goodcodereview.com/module.md"
  },
  {
   "id": "mod-no-shotgun-surgery",
   "level": "module",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "design",
    "coupling"
   ],
   "lang": "core",
   "applies_when": "A change to one rule, format or decision needs small edits in many classes or files.",
   "context": [],
   "tool_gate": null,
   "note": "smell \"Shotgun Surgery\" (Fowler and Beck, through Refactoring.Guru); Microsoft says behaviour spread over several places is a frequent source of errors, and NDepend asks where the knowledge lives; Microsoft's warning about the wrong abstraction is the counterpoint",
   "lacks": null,
   "rule": "Keep one decision in one module, and when a single change needs edits in many classes, move the scattered parts together.",
   "why": "One change repeats in file after file, and the copy you miss is the defect. Refactoring.Guru describes the smell as \"a single responsibility has been split up among a large number of classes\". Microsoft says behaviour spread over several places is \"a frequent source of errors\" and asks for one construct to own it. NDepend says knowledge should exist in one place. The test is concrete: the same rule changes in the same set of files every time.",
   "counterpoint": "\"A feature naturally touches many files.\" A new field does touch the model, the API and the UI. The smell is one internal rule that spreads. Duplication beats coupling to the wrong abstraction, and that is the limit: if the edits are unrelated decisions, merging them would create the wrong abstraction. The signal is that every change of this kind edits the same files.",
   "fail": {
    "language": "text",
    "code": "Adding a currency edits: pricing.py, invoice.py, tax.py, export.py, email.py, api.py, report.py\n# the last two currency changes edited the same seven files"
   },
   "pass": {
    "language": "text",
    "code": "Adding a currency edits: money.py   (rounding, formatting and tax rules live there)"
   },
   "sources": [
    "https://refactoring.guru/smells/shotgun-surgery",
    "https://learn.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/architectural-principles",
    "https://blog.ndepend.com/shotgun-surgery"
   ],
   "orgs": [
    {
     "id": "refactoring-guru",
     "name": "Refactoring.Guru",
     "sources": [
      "https://refactoring.guru/smells/shotgun-surgery"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/architectural-principles"
     ],
     "strength": "unjudged"
    },
    {
     "id": "ndepend",
     "name": "NDepend",
     "sources": [
      "https://blog.ndepend.com/shotgun-surgery"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/module#mod-no-shotgun-surgery",
   "markdown_url": "https://goodcodereview.com/module.md"
  },
  {
   "id": "mod-no-utils-grab-bag",
   "level": "module",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "architecture",
    "naming"
   ],
   "lang": "core",
   "applies_when": "The diff adds or grows a module named `utils`, `helpers`, `common`, or `misc`.",
   "context": [],
   "tool_gate": null,
   "note": null,
   "lacks": null,
   "rule": "Put a helper in the module that owns the concept, or in a new module named after the concept.",
   "why": "A grab-bag has no owner and no cohesion. It becomes the dependency of everything and the place where unrelated code accretes.",
   "counterpoint": "\"Tiny helpers do not deserve a file.\" Then inline them where they are used (YAGNI applies). When one is shared, naming its concept is the work that makes it findable.",
   "fail": {
    "language": "text",
    "code": "utils.py: parse_date, send_email, price_with_tax, retry"
   },
   "pass": {
    "language": "text",
    "code": "dates.py, notifications.py, pricing.py, retry.py"
   },
   "sources": [
    "https://dev.to/sportebois/software-design-red-flags-wisdom-nuggets-from-john-ousterhout-43i2",
    "https://source.android.com/docs/setup/contribute/api-guidelines",
    "https://docs.gitlab.com/development/software_design/",
    "https://feature-sliced.design/docs/guides/issues/desegmented"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://source.android.com/docs/setup/contribute/api-guidelines"
     ],
     "strength": "supports"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://docs.gitlab.com/development/software_design/"
     ],
     "strength": "supports"
    },
    {
     "id": "fsd",
     "name": "Feature-Sliced Design",
     "sources": [
      "https://feature-sliced.design/docs/guides/issues/desegmented"
     ],
     "strength": "supports"
    }
   ],
   "people": [
    {
     "id": "portebois",
     "name": "Sébastien Portebois",
     "sources": [
      "https://dev.to/sportebois/software-design-red-flags-wisdom-nuggets-from-john-ousterhout-43i2"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "strong",
   "url": "https://goodcodereview.com/module#mod-no-utils-grab-bag",
   "markdown_url": "https://goodcodereview.com/module.md"
  },
  {
   "id": "mod-no-wrong-abstraction",
   "level": "module",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "design"
   ],
   "lang": "core",
   "applies_when": "A shared helper or base class has gained flags or conditionals so different callers can bend it.",
   "context": [],
   "tool_gate": "Ruff FBT001 and FBT002 (boolean positional arguments) catch the usual first sign.",
   "note": null,
   "lacks": null,
   "rule": "Inline an abstraction that has grown flags for its callers.",
   "why": "Extract an abstraction when real call sites need it ([[mod-yagni]]). A wrong one is harder to remove than the duplication it replaced, because each new caller adds a branch until nobody can say what the code is for. After you inline it, wait for the third similar caller before extracting again, so the new shape comes from real call sites.",
   "counterpoint": "Sources disagree. Ousterhout lists repetition as a red flag (one fact in many places), and the AHA/Sandi Metz line prefers duplication to a wrong abstraction. They fit together: dedupe knowledge (a rule, a format, a constant), wait on shape, and extract only when the second real caller shows what is shared.",
   "fail": {
    "language": "text",
    "code": "def render(doc, *, as_pdf=False, for_email=False, legacy=False, skip_header=False): ..."
   },
   "pass": {
    "language": "text",
    "code": "render_pdf(doc), render_email(doc)   # small, similar, independent"
   },
   "sources": [
    "https://shopify.engineering/other-driven-developments",
    "https://thoughtbot.com/blog/the-case-for-wet-tests",
    "https://docs.astral.sh/ruff/rules/boolean-type-hint-positional-argument/",
    "https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction",
    "https://kentcdodds.com/blog/aha-programming",
    "https://dev.to/sportebois/software-design-red-flags-wisdom-nuggets-from-john-ousterhout-43i2"
   ],
   "orgs": [
    {
     "id": "shopify",
     "name": "Shopify",
     "sources": [
      "https://shopify.engineering/other-driven-developments"
     ],
     "strength": "unjudged"
    },
    {
     "id": "thoughtbot",
     "name": "thoughtbot",
     "sources": [
      "https://thoughtbot.com/blog/the-case-for-wet-tests"
     ],
     "strength": "unjudged"
    },
    {
     "id": "astral",
     "name": "Astral",
     "sources": [
      "https://docs.astral.sh/ruff/rules/boolean-type-hint-positional-argument/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "sandi-metz",
     "name": "Sandi Metz",
     "sources": [
      "https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction"
     ],
     "strength": "unjudged"
    },
    {
     "id": "kent-c-dodds",
     "name": "Kent C. Dodds",
     "sources": [
      "https://kentcdodds.com/blog/aha-programming"
     ],
     "strength": "unjudged"
    },
    {
     "id": "portebois",
     "name": "Sébastien Portebois",
     "sources": [
      "https://dev.to/sportebois/software-design-red-flags-wisdom-nuggets-from-john-ousterhout-43i2"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/module#mod-no-wrong-abstraction",
   "markdown_url": "https://goodcodereview.com/module.md"
  },
  {
   "id": "mod-one-name-per-concept",
   "level": "module",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "naming"
   ],
   "lang": "core",
   "applies_when": "The change introduces or reuses names for domain concepts.",
   "context": [],
   "tool_gate": null,
   "note": "the island.is and tum-esi conventions and Microsoft's Azure API guidelines (\"use the same name for the same concept\") back the same-name rule; Refactoring.Guru's alternative classes with different interfaces backs the two-class case, which sits in the Why",
   "lacks": null,
   "rule": "Use one name for one concept across the codebase.",
   "why": "If `customer`, `client`, and `account` all mean the same thing, readers wonder whether they differ. A name that misdescribes behaviour is worse than a vague one. Follow the project glossary, and never choose a name that says something the code does not do. Two classes that perform the same job under different method names make a reader learn the operation twice.",
   "counterpoint": "\"Names are bikeshedding.\" Reviewers should not argue taste. They should check that the name is consistent and true. Vague names (names that could mean many things) are a design smell. Do not rename wholesale in a feature change; make the rename a separate change. An adapter at a boundary is exempt: its job is to present a different interface, and it should say which one it adapts.",
   "fail": {
    "language": "text",
    "code": "get_customer(); load_client(); fetch_account()   # same entity\nUserRepo.fetch(id); AccountRepo.load(id)          # same lookup, two names\ndef profile_data(df): ...   # inspects column types, not performance"
   },
   "pass": {
    "language": "text",
    "code": "get_customer(); load_customer(); fetch_customer()\nUserRepo.get(id); AccountRepo.get(id)\ndef discover_structure(df): ..."
   },
   "sources": [
    "https://dev.to/sportebois/software-design-red-flags-wisdom-nuggets-from-john-ousterhout-43i2",
    "https://github.com/island-is/handbook/blob/master/docs/api-design-guide/naming-conventions.md",
    "https://github.com/tum-esi/common-coding-conventions",
    "https://github.com/microsoft/api-guidelines/blob/vNext/azure/ConsiderationsForServiceDesign.md",
    "https://refactoring.guru/smells/alternative-classes-with-different-interfaces"
   ],
   "orgs": [
    {
     "id": "island",
     "name": "Ísland.is",
     "sources": [
      "https://github.com/island-is/handbook/blob/master/docs/api-design-guide/naming-conventions.md"
     ],
     "strength": "supports"
    },
    {
     "id": "tum",
     "name": "TUM ESI",
     "sources": [
      "https://github.com/tum-esi/common-coding-conventions"
     ],
     "strength": "supports"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://github.com/microsoft/api-guidelines/blob/vNext/azure/ConsiderationsForServiceDesign.md"
     ],
     "strength": "supports"
    },
    {
     "id": "refactoring-guru",
     "name": "Refactoring.Guru",
     "sources": [
      "https://refactoring.guru/smells/alternative-classes-with-different-interfaces"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "portebois",
     "name": "Sébastien Portebois",
     "sources": [
      "https://dev.to/sportebois/software-design-red-flags-wisdom-nuggets-from-john-ousterhout-43i2"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "strong",
   "url": "https://goodcodereview.com/module#mod-one-name-per-concept",
   "markdown_url": "https://goodcodereview.com/module.md"
  },
  {
   "id": "mod-pooled-clients-built-once",
   "level": "module",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "performance"
   ],
   "topics": [
    "resources"
   ],
   "lang": "core",
   "applies_when": "Code creates a network or database client.",
   "context": [],
   "tool_gate": null,
   "note": null,
   "lacks": null,
   "rule": "Build pooled clients once at startup and reuse them.",
   "why": "A client per call defeats connection pooling, leaks sockets, and pays a handshake every time. When a client must be short-lived, close it when the work ends.",
   "counterpoint": "\"A throwaway client is simpler.\" For a one-off script, yes (scope it with `with`). On a server path it exhausts sockets under load.",
   "fail": {
    "language": "csharp",
    "code": "async Task<string> Fetch(string url) {\n    using var client = new HttpClient();     // new pool and socket per call\n    return await client.GetStringAsync(url);\n}"
   },
   "pass": {
    "language": "csharp",
    "code": "static readonly HttpClient Client = new() { Timeout = TimeSpan.FromSeconds(10) };   // once\n\nasync Task<string> Fetch(string url) => await Client.GetStringAsync(url);"
   },
   "sources": [
    "https://learn.microsoft.com/en-us/azure/architecture/antipatterns/improper-instantiation/",
    "https://pkg.go.dev/net/http#Client",
    "https://requests.readthedocs.io/en/latest/user/advanced/",
    "https://nodejs.org/api/http.html#class-httpagent"
   ],
   "orgs": [
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/architecture/antipatterns/improper-instantiation/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://pkg.go.dev/net/http#Client"
     ],
     "strength": "unjudged"
    },
    {
     "id": "requests",
     "name": "Requests project",
     "sources": [
      "https://requests.readthedocs.io/en/latest/user/advanced/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nodejs",
     "name": "OpenJS Foundation",
     "sources": [
      "https://nodejs.org/api/http.html#class-httpagent"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/module#mod-pooled-clients-built-once",
   "markdown_url": "https://goodcodereview.com/module.md"
  },
  {
   "id": "mod-proto-enum-zero-unspecified",
   "level": "module",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "compatibility"
   ],
   "topics": [
    "api",
    "protobuf"
   ],
   "lang": "core",
   "applies_when": "A `.proto` file defines an enum.",
   "context": [],
   "tool_gate": "Protobuf linter (`buf lint`, ENUM_ZERO_VALUE_SUFFIX).",
   "note": "a zero value with no real meaning has Google and Dropbox (UNKNOWN = 0); the _UNSPECIFIED name has Google only; Cabify lints for consistent zero values without naming the convention",
   "lacks": null,
   "rule": "Give every protobuf enum a zero value named `<ENUM>_UNSPECIFIED` with no real meaning.",
   "why": "A reader that sees an unset or unknown field gets zero. If zero means a real state, such as `PAID`, a missing field reads as a payment.",
   "counterpoint": "\"Zero as the common default saves a field on the wire.\" The saving is a byte on a field already at its most common value, and the cost is a silent wrong state. Published enums keep their numbers, so the fix for an existing enum is a new unspecified name only if no value has to be renumbered.",
   "fail": {
    "language": "proto",
    "code": "enum Status {\n  PAID = 0;\n  REFUNDED = 1;\n}"
   },
   "pass": {
    "language": "proto",
    "code": "enum Status {\n  STATUS_UNSPECIFIED = 0;\n  PAID = 1;\n  REFUNDED = 2;\n}"
   },
   "sources": [
    "https://protobuf.dev/best-practices/dos-donts/",
    "https://dropbox.tech/infrastructure/courier-dropbox-migration-to-grpc",
    "https://buf.build/docs/lint/rules/",
    "https://github.com/envoyproxy/envoy/blob/main/api/STYLE.md",
    "https://github.com/open-telemetry/opentelemetry-proto/raw/refs/heads/main/opentelemetry/proto/trace/v1/trace.proto"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://protobuf.dev/best-practices/dos-donts/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "dropbox",
     "name": "Dropbox",
     "sources": [
      "https://dropbox.tech/infrastructure/courier-dropbox-migration-to-grpc"
     ],
     "strength": "unjudged"
    },
    {
     "id": "buf",
     "name": "Buf",
     "sources": [
      "https://buf.build/docs/lint/rules/"
     ],
     "strength": "supports"
    },
    {
     "id": "envoy",
     "name": "Envoy",
     "sources": [
      "https://github.com/envoyproxy/envoy/blob/main/api/STYLE.md"
     ],
     "strength": "supports"
    },
    {
     "id": "opentelemetry",
     "name": "OpenTelemetry",
     "sources": [
      "https://github.com/open-telemetry/opentelemetry-proto/raw/refs/heads/main/opentelemetry/proto/trace/v1/trace.proto"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "strong",
   "url": "https://goodcodereview.com/module#mod-proto-enum-zero-unspecified",
   "markdown_url": "https://goodcodereview.com/module.md"
  },
  {
   "id": "mod-single-writer-per-state",
   "level": "module",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "correctness"
   ],
   "topics": [
    "concurrency",
    "ownership"
   ],
   "lang": "core",
   "applies_when": "More than one module, worker or process writes the same state.",
   "context": [],
   "tool_gate": null,
   "note": "one writer at a time is backed by cursor/plugins, Netflix (an exclusive queue lets one worker evaluate a workflow at a time), Cabify (CI deploy runs that were not mutually exclusive reverted each other, replaced by one GitOps controller) and GitHub (writes to one reference are serialised); giving each writer its own state and merging on read has cursor/plugins and GitHub only, so it sits in Why",
   "lacks": null,
   "rule": "Let only one writer change a piece of state at a time, through a single owner, a queue that serialises the writes, or compare-and-swap, never by convention.",
   "why": "A convention such as \"only write your own field\" is not concurrency control. Two workers writing different fields of one file still overwrite each other, and two deploy runs that overlap can undo each other's changes. Better still, split the state so that each writer owns its part and readers merge the parts: writers to different parts then never wait for each other.",
   "counterpoint": "\"A lock is simpler than a queue or an owner.\" A lock works only when the store rejects a writer whose lock has expired; see [[sys-lock-fencing]]. Without that check, a paused holder can wake up and write after another writer took over.",
   "fail": {
    "language": "text",
    "code": "worker A and worker B each load state.json, set their own key, and save the whole file"
   },
   "pass": {
    "language": "text",
    "code": "workers send updates to one queue; a single consumer applies them to state.json in order\nor: state/a.json and state/b.json, one writer each; readers merge them"
   },
   "sources": [
    "https://github.com/cursor/plugins/blob/main/pstack/skills/principle-separate-before-serializing-shared-state/SKILL.md",
    "https://github.com/cursor/plugins/blob/main/pstack/skills/architect/references/design-red-flags.md",
    "https://netflixtechblog.com/netflix-conductor-the-next-chapter-41ad21067649",
    "https://tech.cabify.com/blog/engineering/scaling-argocd-to-dozens-testing-environments",
    "https://github.blog/engineering/architecture-optimization/the-bridge-to-agent-scale-git-infrastructure"
   ],
   "orgs": [
    {
     "id": "cursor",
     "name": "Cursor",
     "sources": [
      "https://github.com/cursor/plugins/blob/main/pstack/skills/principle-separate-before-serializing-shared-state/SKILL.md",
      "https://github.com/cursor/plugins/blob/main/pstack/skills/architect/references/design-red-flags.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "netflix",
     "name": "Netflix",
     "sources": [
      "https://netflixtechblog.com/netflix-conductor-the-next-chapter-41ad21067649"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cabify",
     "name": "Cabify",
     "sources": [
      "https://tech.cabify.com/blog/engineering/scaling-argocd-to-dozens-testing-environments"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://github.blog/engineering/architecture-optimization/the-bridge-to-agent-scale-git-infrastructure"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/module#mod-single-writer-per-state",
   "markdown_url": "https://goodcodereview.com/module.md"
  },
  {
   "id": "mod-vetted-crypto-only",
   "level": "module",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "crypto"
   ],
   "lang": "core",
   "applies_when": "A module encrypts or decrypts data.",
   "context": [],
   "tool_gate": "A grep for `MODE_ECB`, `MODE_CBC` with no MAC, and nonces built from constants.",
   "note": null,
   "lacks": null,
   "rule": "Encrypt with an authenticated mode such as AES-GCM, and never reuse a nonce under one key.",
   "why": "Unauthenticated modes allow silent tampering, and ECB also leaks the structure of the plaintext. Reusing a nonce under one key breaks GCM completely: it leaks the XOR of the plaintexts and the authentication key. Take the call from a maintained library and never write your own cipher. Keys belong in a KMS or secrets manager, never in source; [[sys-secrets-and-errors]] covers where secrets live at runtime.",
   "counterpoint": "\"The library API is awkward, so a wrapper is safer.\" Wrap the vetted call, do not replace it. Review the wrapper only for nonce handling and key sourcing.",
   "fail": {
    "language": "python",
    "code": "cipher = AES.new(KEY, AES.MODE_ECB)\nct = bytes(b ^ k for b, k in zip(msg, KEY * 99))"
   },
   "pass": {
    "language": "python",
    "code": "aead = AESGCM(key_from_kms)\nct = aead.encrypt(os.urandom(12), msg, aad)      # fresh nonce per message"
   },
   "sources": [
    "https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html",
    "https://top10.owasp.org/2025/A04_2025-Cryptographic_Failures",
    "https://www.rfc-editor.org/rfc/rfc5116.html",
    "https://csrc.nist.gov/pubs/sp/800/38/d/final",
    "https://pkg.go.dev/crypto/cipher#NewGCM",
    "https://developers.google.com/tink"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html",
      "https://top10.owasp.org/2025/A04_2025-Cryptographic_Failures"
     ],
     "strength": "unjudged"
    },
    {
     "id": "ietf",
     "name": "IETF",
     "sources": [
      "https://www.rfc-editor.org/rfc/rfc5116.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nist",
     "name": "NIST",
     "sources": [
      "https://csrc.nist.gov/pubs/sp/800/38/d/final"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://pkg.go.dev/crypto/cipher#NewGCM",
      "https://developers.google.com/tink"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/module#mod-vetted-crypto-only",
   "markdown_url": "https://goodcodereview.com/module.md"
  },
  {
   "id": "mod-yagni",
   "level": "module",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "maintainability",
    "process"
   ],
   "topics": [
    "design",
    "scope"
   ],
   "lang": "core",
   "applies_when": "The code adds or keeps an abstraction, parameter, hook or option.",
   "context": [],
   "tool_gate": null,
   "note": "The MediaWiki architecture page labels itself a draft. The Microsoft playbook checklist may echo Google's wording and the extensibility guideline is indirect. Shopify backs the second-use clause.",
   "lacks": null,
   "rule": "Do not build an abstraction or capability for a need that does not exist yet; add it when the need or a second use appears.",
   "why": "Speculative flexibility costs reading time every day and is usually the wrong shape when the need arrives. Reviewers cannot verify a use that does not exist.",
   "counterpoint": "\"But we will surely need it.\" Then add it when you do, in a change that has the real second caller to shape it. Optimising for easy change means simple code, not built-in flexibility. The cost of waiting is low, and the cost of the wrong guess is not. When the need is certain and a retrofit would be costly, as for a public API that cannot change later, weigh that cost before waiting.",
   "fail": {
    "language": "text",
    "code": "class NotifierFactory: ...   # one product, one caller\ndef send(msg, retries=3, backoff=None, hook=None): ...   # nobody passes these"
   },
   "pass": {
    "language": "text",
    "code": "def send_welcome_email(user): ..."
   },
   "sources": [
    "https://kentcdodds.com/blog/aha-programming",
    "https://google.github.io/eng-practices/review/reviewer/looking-for.html",
    "https://www.mediawiki.org/wiki/Architecture_guidelines",
    "https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/designing-for-extensibility",
    "https://microsoft.github.io/code-with-engineering-playbook/code-reviews/process-guidance/reviewer-guidance/",
    "https://shopify.engineering/other-driven-developments",
    "https://martinfowler.com/bliki/Yagni.html",
    "https://henrikwarne.com/2024/01/10/tidy-first/",
    "https://github.com/NVIDIA/Model-Optimizer/blob/main/CONTRIBUTING.md",
    "https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/abstractions-abstract-types-and-interfaces",
    "https://go.dev/wiki/CodeReviewComments",
    "https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/hexagonal-architecture.html"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://google.github.io/eng-practices/review/reviewer/looking-for.html",
      "https://go.dev/wiki/CodeReviewComments"
     ],
     "strength": "unjudged"
    },
    {
     "id": "wikimedia",
     "name": "Wikimedia (MediaWiki)",
     "sources": [
      "https://www.mediawiki.org/wiki/Architecture_guidelines"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/designing-for-extensibility",
      "https://microsoft.github.io/code-with-engineering-playbook/code-reviews/process-guidance/reviewer-guidance/",
      "https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/abstractions-abstract-types-and-interfaces"
     ],
     "strength": "unjudged"
    },
    {
     "id": "shopify",
     "name": "Shopify",
     "sources": [
      "https://shopify.engineering/other-driven-developments"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nvidia",
     "name": "NVIDIA",
     "sources": [
      "https://github.com/NVIDIA/Model-Optimizer/blob/main/CONTRIBUTING.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/hexagonal-architecture.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "kent-c-dodds",
     "name": "Kent C. Dodds",
     "sources": [
      "https://kentcdodds.com/blog/aha-programming"
     ],
     "strength": "unjudged"
    },
    {
     "id": "fowler",
     "name": "Martin Fowler",
     "sources": [
      "https://martinfowler.com/bliki/Yagni.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "warne",
     "name": "Henrik Warne",
     "sources": [
      "https://henrikwarne.com/2024/01/10/tidy-first/"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/module#mod-yagni",
   "markdown_url": "https://goodcodereview.com/module.md"
  },
  {
   "id": "prod-a11y-audit-before-launch",
   "level": "production",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "accessibility",
    "usability"
   ],
   "topics": [
    "frontend",
    "release"
   ],
   "lang": "core",
   "applies_when": "A new UI service or a major redesign is about to open to the public.",
   "context": [
    "ui"
   ],
   "tool_gate": "none; a launch checklist item that links the audit report.",
   "note": "3 orgs for an audit with assistive technology (GOV.UK, W3C, Deque), but only GOV.UK ties it to before a public launch, which is the card's point; Section508.gov weak; the GOV.UK duty is a UK legal duty, so this card names WCAG 2.2 AA as the standard and claims no law elsewhere",
   "lacks": null,
   "rule": "Before a public launch, run an accessibility audit against WCAG 2.2 AA that includes assistive technology, and link the report in the launch checklist.",
   "why": "Quick checks and automated tools are a first pass, not an evaluation. A page can pass both and still block a screen-reader user from finishing the task. Finding that after launch means fixing it under load and in public.",
   "counterpoint": "\"We have no budget for an external audit.\" The auditor can be in-house, if they are trained and did not build the service. Cut scope instead of the audit: audit the main task flows first, and say in the report which parts are not covered.",
   "fail": {
    "language": "text",
    "code": "Launch checklist\n[x] Load test      [x] Rollback plan      [x] Monitoring\n(no accessibility line)"
   },
   "pass": {
    "language": "text",
    "code": "Launch checklist\n[x] Accessibility audit vs WCAG 2.2 AA, with screen reader, magnifier, and voice input.\n    Report: docs/audit-2026-10.pdf. Open issues: 2, both minor, owner and date set."
   },
   "sources": [
    "https://www.gov.uk/service-manual/helping-people-to-use-your-service/making-your-service-accessible-an-introduction",
    "https://www.w3.org/WAI/test-evaluate/preliminary/",
    "https://alphagov.github.io/accessibility-tool-audit/",
    "https://www.section508.gov/test/",
    "https://www.w3.org/WAI/test-evaluate/",
    "https://www.deque.com/web-accessibility-beginners-guide/",
    "https://www2.gov.bc.ca/gov/content/home/accessible-government/toolkit/testing-and-auditing",
    "https://design.va.gov/accessibility/"
   ],
   "orgs": [
    {
     "id": "govuk",
     "name": "GOV.UK",
     "sources": [
      "https://www.gov.uk/service-manual/helping-people-to-use-your-service/making-your-service-accessible-an-introduction",
      "https://alphagov.github.io/accessibility-tool-audit/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "w3c",
     "name": "W3C",
     "sources": [
      "https://www.w3.org/WAI/test-evaluate/preliminary/",
      "https://www.w3.org/WAI/test-evaluate/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "section508",
     "name": "Section508.gov",
     "sources": [
      "https://www.section508.gov/test/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "deque",
     "name": "Deque",
     "sources": [
      "https://www.deque.com/web-accessibility-beginners-guide/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "bcgov",
     "name": "Government of British Columbia",
     "sources": [
      "https://www2.gov.bc.ca/gov/content/home/accessible-government/toolkit/testing-and-auditing"
     ],
     "strength": "supports"
    },
    {
     "id": "va",
     "name": "US Department of Veterans Affairs",
     "sources": [
      "https://design.va.gov/accessibility/"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/production#prod-a11y-audit-before-launch",
   "markdown_url": "https://goodcodereview.com/production.md"
  },
  {
   "id": "prod-alerts-symptoms-golden-signals",
   "level": "production",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "observability"
   ],
   "topics": [
    "monitoring",
    "alerting"
   ],
   "lang": "core",
   "applies_when": "A service ships alerts or dashboards.",
   "context": [],
   "tool_gate": null,
   "note": null,
   "lacks": null,
   "rule": "Page on user-visible symptoms, such as error rate and latency, not on causes.",
   "why": "Cause-based pages (CPU high) wake people without user impact. A mean hides the slow tail, and failed-request latency mixed with successes hides both. Require an error-rate alert to hold for a few minutes, so one blip does not page. Also cover latency, traffic, errors, and saturation, track latency as a distribution, and delete alerts that never fire.",
   "counterpoint": "Cause alerts help diagnosis. Keep them as tickets or dashboard hints, not pages. The Honeycomb wide-event camp argues against pre-set metrics, while the SRE book favors simple metric-based paging, so this card does not ban events, only cause-paging.",
   "fail": {
    "language": "text",
    "code": "alert: cpu > 80%  -> page\ndashboard: avg latency only"
   },
   "pass": {
    "language": "text",
    "code": "alert: error ratio > X, for 5m -> page\nlatency p50/p99 histogram, errors split from successes"
   },
   "sources": [
    "https://sre.google/sre-book/monitoring-distributed-systems/",
    "https://prometheus.io/docs/practices/alerting/",
    "https://grafana.com/docs/grafana/latest/alerting/guides/best-practices/",
    "https://www.datadoghq.com/blog/monitoring-101-alerting/"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://sre.google/sre-book/monitoring-distributed-systems/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "prometheus",
     "name": "Prometheus",
     "sources": [
      "https://prometheus.io/docs/practices/alerting/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "grafana",
     "name": "Grafana Labs",
     "sources": [
      "https://grafana.com/docs/grafana/latest/alerting/guides/best-practices/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "datadog",
     "name": "Datadog",
     "sources": [
      "https://www.datadoghq.com/blog/monitoring-101-alerting/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/production#prod-alerts-symptoms-golden-signals",
   "markdown_url": "https://goodcodereview.com/production.md"
  },
  {
   "id": "prod-canary-staged-rollout",
   "level": "production",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "reliability"
   ],
   "topics": [
    "deploys",
    "release"
   ],
   "lang": "core",
   "applies_when": "A change, config or dependency update is rolled out to production.",
   "context": [],
   "tool_gate": "Deploy pipeline with canary stage and automatic abort.",
   "note": "Canary: Google, OWASP, Microsoft (a vendor). Abort by rule: Google, AWS CodeDeploy (a vendor) and Microsoft (a vendor, halts on a health alert). Anthropic (a vendor) backs only staged rollout and says it has no circuit breaker yet; not a counted organisation",
   "lacks": null,
   "rule": "Roll out in stages behind a canary, and abort by rule, not by judgment.",
   "why": "A 100% push exposes everyone to the bug. A canary must be large and long enough to be representative. Errors of 20% on 5% of traffic are only 1% overall, so tie the canary to your error budget.",
   "counterpoint": "\"Hotfixes can't wait.\" Then keep the canary short, not absent. Fleet tools should also have velocity limits. A hermetic, reproducible build tied to one revision ([[code-build-hermetic]]) is the precondition for rollback to mean anything.",
   "fail": {
    "language": "text",
    "code": "deploy: all hosts at once, no comparison, rollback = \"ask someone\""
   },
   "pass": {
    "language": "text",
    "code": "# compare the canary to a control on a few chosen metrics\n5% canary 30m -> compare error rate, p99 -> auto-rollback if worse -> 25% -> 100%"
   },
   "sources": [
    "https://sre.google/workbook/canarying-releases/",
    "https://sre.google/sre-book/release-engineering/",
    "https://aws.amazon.com/builders-library/minimizing-correlated-failures-in-distributed-systems/",
    "https://owasp.org/Top10/2025/A03_2025-Software_Supply_Chain_Failures/",
    "https://x.com/addyosmani/status/2114604912520921136",
    "https://learn.microsoft.com/en-us/azure/well-architected/operational-excellence/safe-deployments",
    "https://docs.aws.amazon.com/codedeploy/latest/userguide/deployments-rollback-and-redeploy.html"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://sre.google/workbook/canarying-releases/",
      "https://sre.google/sre-book/release-engineering/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://aws.amazon.com/builders-library/minimizing-correlated-failures-in-distributed-systems/",
      "https://docs.aws.amazon.com/codedeploy/latest/userguide/deployments-rollback-and-redeploy.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://owasp.org/Top10/2025/A03_2025-Software_Supply_Chain_Failures/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "anthropic",
     "name": "Anthropic",
     "sources": [
      "https://x.com/addyosmani/status/2114604912520921136"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/well-architected/operational-excellence/safe-deployments"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/production#prod-canary-staged-rollout",
   "markdown_url": "https://goodcodereview.com/production.md"
  },
  {
   "id": "prod-kill-switch-fallback",
   "level": "production",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "reliability"
   ],
   "topics": [
    "resilience",
    "flags"
   ],
   "lang": "core",
   "applies_when": "A new risky feature or a dependency fallback is added.",
   "context": [],
   "tool_gate": null,
   "note": "switch off without a deploy - Cloudflare (kill switches through global config), GitHub (Flipper rollout and rollback), Slack (feature flags with instant rollback), Dropbox (rollback for experiments and gates); exercise the fallback - AWS (tests with the cache disabled), Cloudflare (regular cut tests; an untested fail-open path), Shopify (repeated Game Days in production), Dropbox (monthly datacenter failovers); on a schedule - Cloudflare, Shopify, Dropbox; Cloudflare's overload tests are a stated plan; Anthropic (a vendor) also says \"Changes behind feature flags can be disabled without deploying\", not a counted third org",
   "lacks": null,
   "rule": "Make a risky feature switchable off without a deploy, and exercise every fallback path in test or in production on a schedule.",
   "why": "A deploy is the slowest way to stop a feature that is hurting users. An untested fallback fails exactly when you need it: a fail-open path that never ran under load can overload the systems behind it, and a kill switch applied to a case it never met can raise its own error. Run the service with the dependency turned off, such as the cache disabled, and check that the safeguards hold.",
   "counterpoint": "\"Flags add branches.\" True for release flags, so delete each one after the rollout. A kill switch is meant to stay: keep it, give it an owner, and exercise it with the fallbacks, so it still works the day you need it. For runbook steps that a person runs, such as a rollback or a failover, see [[inc-rollback-path-tested]].",
   "fail": {
    "language": "python",
    "code": "if redis_down:\n    return backup_lookup(key)   # never run since it was written\nnew_checkout(cart)              # no flag; turning it off needs a deploy"
   },
   "pass": {
    "language": "python",
    "code": "if flags.enabled(\"checkout_v2\"):   # runtime toggle, default off\n    new_checkout(cart)\n# chaos job, weekly: block Redis for 10 minutes and assert backup_lookup serves"
   },
   "sources": [
    "https://aws.amazon.com/builders-library/caching-challenges-and-strategies/",
    "https://blog.cloudflare.com/18-november-2025-outage/",
    "https://blog.cloudflare.com/5-december-2025-outage/",
    "https://blog.cloudflare.com/cloudflare-incident-on-november-14-2024-resulting-in-lost-logs/",
    "https://github.blog/engineering/engineering-principles/how-we-improved-availability-through-iterative-simplification",
    "https://slack.engineering/slack-ai-the-path-to-multi-cloud/",
    "https://dropbox.tech/infrastructure/lessons-learned-in-incident-management",
    "https://dropbox.tech/infrastructure/disaster-readiness-test-failover-blackhole-sjc",
    "https://shopify.engineering/four-steps-creating-effective-game-day-tests",
    "https://x.com/addyosmani/status/2114604912520921136"
   ],
   "orgs": [
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://aws.amazon.com/builders-library/caching-challenges-and-strategies/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cloudflare",
     "name": "Cloudflare",
     "sources": [
      "https://blog.cloudflare.com/18-november-2025-outage/",
      "https://blog.cloudflare.com/5-december-2025-outage/",
      "https://blog.cloudflare.com/cloudflare-incident-on-november-14-2024-resulting-in-lost-logs/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://github.blog/engineering/engineering-principles/how-we-improved-availability-through-iterative-simplification"
     ],
     "strength": "unjudged"
    },
    {
     "id": "slack",
     "name": "Slack",
     "sources": [
      "https://slack.engineering/slack-ai-the-path-to-multi-cloud/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "dropbox",
     "name": "Dropbox",
     "sources": [
      "https://dropbox.tech/infrastructure/lessons-learned-in-incident-management",
      "https://dropbox.tech/infrastructure/disaster-readiness-test-failover-blackhole-sjc"
     ],
     "strength": "unjudged"
    },
    {
     "id": "shopify",
     "name": "Shopify",
     "sources": [
      "https://shopify.engineering/four-steps-creating-effective-game-day-tests"
     ],
     "strength": "unjudged"
    },
    {
     "id": "anthropic",
     "name": "Anthropic",
     "sources": [
      "https://x.com/addyosmani/status/2114604912520921136"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/production#prod-kill-switch-fallback",
   "markdown_url": "https://goodcodereview.com/production.md"
  },
  {
   "id": "prod-latency-slo-percentile",
   "level": "production",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "performance",
    "observability"
   ],
   "topics": [
    "slo"
   ],
   "lang": "core",
   "applies_when": "A latency SLO, dashboard, or alert is defined for a service.",
   "context": [],
   "tool_gate": "Lint alert rules for avg() over latency or over pre-computed quantiles.",
   "note": "Never the mean: Google, AWS (a vendor), Microsoft (a vendor). Prometheus warns only against averaging quantiles; OpenTelemetry is a definition page",
   "lacks": null,
   "rule": "Define latency SLOs and alerts on high percentiles, never on the mean.",
   "why": "A mean can look healthy while 1 request in 100 takes seconds, and those are the users who leave. Averaging per-instance quantiles is also wrong: quantiles do not add, so compute the percentile from a histogram, the one form that aggregates correctly across replicas.",
   "counterpoint": "\"Percentiles are noisy at low traffic.\" They are, so set a minimum request count before the alert fires, or alert on the error budget burn rate. Pick bucket edges around the objective, or the percentile will be a guess between two wide buckets.",
   "fail": {
    "language": "text",
    "code": "alert: avg(rate(request_seconds_sum[5m]) / rate(request_seconds_count[5m])) > 0.3\navg(http_latency{quantile=\"0.99\"})        # averages summaries across pods"
   },
   "pass": {
    "language": "text",
    "code": "histogram_quantile(0.99, sum by (le) (rate(request_seconds_bucket[5m]))) > 0.3\nSLO: 99% of requests under 300 ms over 28 days"
   },
   "sources": [
    "https://sre.google/sre-book/service-level-objectives/",
    "https://sre.google/sre-book/monitoring-distributed-systems/",
    "https://builder.aws.com/content/3EuxPBdIiiUhB5IK47p3O3fxhy7/instrumenting-distributed-systems-for-operational-visibility",
    "https://prometheus.io/docs/practices/histograms/",
    "https://opentelemetry.io/docs/specs/otel/metrics/api/",
    "https://learn.microsoft.com/en-us/azure/well-architected/performance-efficiency/monitoring"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://sre.google/sre-book/service-level-objectives/",
      "https://sre.google/sre-book/monitoring-distributed-systems/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://builder.aws.com/content/3EuxPBdIiiUhB5IK47p3O3fxhy7/instrumenting-distributed-systems-for-operational-visibility"
     ],
     "strength": "unjudged"
    },
    {
     "id": "prometheus",
     "name": "Prometheus",
     "sources": [
      "https://prometheus.io/docs/practices/histograms/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "opentelemetry",
     "name": "OpenTelemetry",
     "sources": [
      "https://opentelemetry.io/docs/specs/otel/metrics/api/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/well-architected/performance-efficiency/monitoring"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/production#prod-latency-slo-percentile",
   "markdown_url": "https://goodcodereview.com/production.md"
  },
  {
   "id": "prod-load-test-before-launch",
   "level": "production",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "performance",
    "reliability"
   ],
   "topics": [
    "release",
    "capacity"
   ],
   "lang": "core",
   "applies_when": "A new service, a new feature with its own traffic, or a large architecture change is about to launch.",
   "context": [],
   "tool_gate": "Load-test job with thresholds (k6, Gatling) in the release pipeline.",
   "note": null,
   "lacks": null,
   "rule": "Load test before launch, past the expected peak, with pass and fail thresholds written down.",
   "why": "Overload behaviour is hard to predict from code, and launch traffic often beats the estimate. Test the whole workload on production-like infrastructure: testing one component, on smaller machines, or only up to the expected load misses the queue that fills first.",
   "counterpoint": "\"A production-sized test environment costs too much.\" Size the test to the risk. A scaled-down environment is acceptable for a non-critical workload, and skipping the test is low risk for a small one. For a launch that could take down a shared dependency, the test is cheaper than the outage. Roll out in stages either way ([[prod-canary-staged-rollout]]).",
   "fail": {
    "language": "text",
    "code": "launch checklist: \"load test: n/a, staging handled 50 rps fine\"\nexpected peak 2,000 rps; no test above 1,000"
   },
   "pass": {
    "language": "text",
    "code": "k6 stress run on the prod-sized env: ramp to 3x expected peak (6,000 rps)\nthresholds: p99 < 400 ms, errors < 0.1%; result linked in the launch doc"
   },
   "sources": [
    "https://sre.google/sre-book/reliable-product-launches/",
    "https://learn.microsoft.com/en-us/azure/well-architected/performance-efficiency/performance-test",
    "https://docs.aws.amazon.com/wellarchitected/latest/performance-efficiency-pillar/perf_process_culture_load_test.html",
    "https://grafana.com/docs/k6/latest/testing-guides/test-types/",
    "https://grafana.com/docs/k6/latest/using-k6/thresholds/"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://sre.google/sre-book/reliable-product-launches/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/well-architected/performance-efficiency/performance-test"
     ],
     "strength": "unjudged"
    },
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://docs.aws.amazon.com/wellarchitected/latest/performance-efficiency-pillar/perf_process_culture_load_test.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "grafana",
     "name": "Grafana Labs",
     "sources": [
      "https://grafana.com/docs/k6/latest/testing-guides/test-types/",
      "https://grafana.com/docs/k6/latest/using-k6/thresholds/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/production#prod-load-test-before-launch",
   "markdown_url": "https://goodcodereview.com/production.md"
  },
  {
   "id": "prod-profile-on-demand",
   "level": "production",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "performance",
    "observability"
   ],
   "topics": [],
   "lang": "core",
   "applies_when": "A service runs in production where CPU, memory, or latency problems would need a diagnosis.",
   "context": [],
   "tool_gate": null,
   "note": null,
   "lacks": null,
   "rule": "Make a CPU and memory profile of a production instance obtainable, through a continuous profiler or a private profile endpoint.",
   "why": "Test environments rarely match production load, so the hot path in a test run is often not the hot path in production. When latency spikes, a redeploy to add profiling loses the moment and changes the system you wanted to observe, so the profiler is in place before the incident and the runbook says how to reach it.",
   "counterpoint": "\"Profiling costs CPU and opens an endpoint.\" Sampling profilers stay at a few percent, and you can profile one replica at a time. Bind the endpoint to localhost or an admin port. A profile endpoint on a public listener is a security fail.",
   "fail": {
    "language": "text",
    "code": "runbook \"high CPU\": restart the pod\nno profiler agent, no /debug/pprof, no sampling profiler in the image"
   },
   "pass": {
    "language": "text",
    "code": "continuous profiler agent at ~2% overhead, 14-day retention\nor: pprof on 127.0.0.1:6060 only, reached by port-forward; steps in runbook"
   },
   "sources": [
    "https://go.dev/doc/diagnostics",
    "https://pkg.go.dev/net/http/pprof",
    "https://grafana.com/docs/pyroscope/latest/introduction/continuous-profiling/",
    "https://docs.cloud.google.com/profiler/docs/about-profiler",
    "https://docs.python.org/3/library/profile.html",
    "https://www.brendangregg.com/flamegraphs.html"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://go.dev/doc/diagnostics",
      "https://pkg.go.dev/net/http/pprof",
      "https://docs.cloud.google.com/profiler/docs/about-profiler"
     ],
     "strength": "unjudged"
    },
    {
     "id": "grafana",
     "name": "Grafana Labs",
     "sources": [
      "https://grafana.com/docs/pyroscope/latest/introduction/continuous-profiling/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://docs.python.org/3/library/profile.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "brendan-gregg",
     "name": "Brendan Gregg",
     "sources": [
      "https://www.brendangregg.com/flamegraphs.html"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/production#prod-profile-on-demand",
   "markdown_url": "https://goodcodereview.com/production.md"
  },
  {
   "id": "prod-shadow-compare-before-cutover",
   "level": "production",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "correctness",
    "reliability"
   ],
   "topics": [
    "migration",
    "release"
   ],
   "lang": "core",
   "applies_when": "Reads, writes, or a pipeline's traffic move from an old system to a new one, such as a new data store, a database upgrade, or a rewritten service.",
   "context": [
    "data"
   ],
   "tool_gate": null,
   "note": "Netflix (ran the old and new ad-event pipelines in parallel and diffed their output on live traffic), Stripe (compared point-in-time snapshots of source and target shards before the traffic switch), Airbnb (replayed captured production queries on MySQL 5.7 and 8.0 and diffed the results), GitHub (a Scientist experiment ran a candidate code block beside the control and compared results and timing); Cabify checks for zero replication lag before a swap, which is a precondition and not a diff, so it is not cited",
   "lacks": null,
   "rule": "Before you move traffic to a new system, run it beside the old one on live or captured production traffic, diff the results, and cut over only when every mismatch is fixed or explained.",
   "why": "Fixtures cover the cases someone thought of, and production holds the rest. Airbnb's replay of real queries against MySQL 5.7 and 8.0 found queries whose row order was never fixed, and an application that sent the same cacheable query over and over. Neither shows up in a test suite. A diff on real traffic finds these while the old system still answers users, so a mismatch costs a log line instead of an incident.",
   "counterpoint": "\"Running both is slow and doubles the load.\" It is slower than a flag-day switch, and Netflix says that is the reason it trusts the result. Keep the cost down: shadow a sample, call the new system off the request path, and never shadow a call with side effects such as a charge or an email. For writes, compare data snapshots after replication, as Stripe does, instead of sending each write twice. A refactor inside one process needs a characterization test, not production traffic. See [[chg-refactor-has-behaviour-pin]].",
   "fail": {
    "language": "python",
    "code": "def get_order(order_id):\n    return new_store.get(order_id)    # cut over in one step; never compared on real reads"
   },
   "pass": {
    "language": "python",
    "code": "def get_order(order_id):\n    old = old_store.get(order_id)     # users still get the old answer\n    try:\n        if new_store.get(order_id) != old:\n            shadow_mismatch.inc()\n            log.info(\"shadow mismatch\", extra={\"order_id\": order_id})\n    except Exception:\n        shadow_error.inc()\n    return old\n# the cutover flag flips once shadow_mismatch is zero or every mismatch is explained"
   },
   "sources": [
    "https://netflixtechblog.com/behind-the-scenes-evolving-netflixs-ads-event-pipeline-for-live-part-ii-826ebf9ad9fb",
    "https://stripe.dev/blog/how-stripes-document-databases-supported-99.999-uptime-with-zero-downtime-data-migrations",
    "https://airbnb.tech/infrastructure/beyond-synthetic-testing-capturing-and-replaying-real-database-workloads-at-airbnb/",
    "https://github.blog/engineering/engineering-principles/how-we-improved-availability-through-iterative-simplification",
    "https://americanexpress.io/migrating-the-payments-network-twice",
    "https://microsoft.github.io/code-with-engineering-playbook/automated-testing/shadow-testing/",
    "https://shopify.engineering/scaling-inventory-reservations"
   ],
   "orgs": [
    {
     "id": "netflix",
     "name": "Netflix",
     "sources": [
      "https://netflixtechblog.com/behind-the-scenes-evolving-netflixs-ads-event-pipeline-for-live-part-ii-826ebf9ad9fb"
     ],
     "strength": "unjudged"
    },
    {
     "id": "stripe",
     "name": "Stripe",
     "sources": [
      "https://stripe.dev/blog/how-stripes-document-databases-supported-99.999-uptime-with-zero-downtime-data-migrations"
     ],
     "strength": "unjudged"
    },
    {
     "id": "airbnb",
     "name": "Airbnb",
     "sources": [
      "https://airbnb.tech/infrastructure/beyond-synthetic-testing-capturing-and-replaying-real-database-workloads-at-airbnb/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://github.blog/engineering/engineering-principles/how-we-improved-availability-through-iterative-simplification"
     ],
     "strength": "unjudged"
    },
    {
     "id": "amex",
     "name": "American Express",
     "sources": [
      "https://americanexpress.io/migrating-the-payments-network-twice"
     ],
     "strength": "supports"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://microsoft.github.io/code-with-engineering-playbook/automated-testing/shadow-testing/"
     ],
     "strength": "supports"
    },
    {
     "id": "shopify",
     "name": "Shopify",
     "sources": [
      "https://shopify.engineering/scaling-inventory-reservations"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "strong",
   "url": "https://goodcodereview.com/production#prod-shadow-compare-before-cutover",
   "markdown_url": "https://goodcodereview.com/production.md"
  },
  {
   "id": "prod-slo-error-budget",
   "level": "production",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "reliability",
    "observability"
   ],
   "topics": [
    "slo"
   ],
   "lang": "core",
   "applies_when": "A service has users or dependents who rely on its availability.",
   "context": [],
   "tool_gate": null,
   "note": "no source yet; first-hand reasoning",
   "lacks": null,
   "rule": "Define an SLO with an indicator and window, and use its remaining error budget to gate risky releases.",
   "why": "Without a stated target, nobody can say whether reliability work or a freeze is warranted, and every incident is argued from feelings.",
   "counterpoint": "\"SLOs are bureaucracy for small teams.\" A one-line target is enough. Skip it for internal tools with no users.",
   "fail": {
    "language": "text",
    "code": "\"we aim for high availability\" ; no SLI, alerts at arbitrary thresholds"
   },
   "pass": {
    "language": "text",
    "code": "SLO: 99.9% of requests succeed in 300ms over 30d ; budget burn alert; freeze at 0"
   },
   "sources": [
    "https://sre.google/workbook/error-budget-policy/",
    "https://handbook.gitlab.com/handbook/engineering/infrastructure-platforms/database/",
    "https://www.atlassian.com/blog/atlassian-engineering/unlocking-reliability-with-splunk-smartstore"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://sre.google/workbook/error-budget-policy/"
     ],
     "strength": "supports"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://handbook.gitlab.com/handbook/engineering/infrastructure-platforms/database/"
     ],
     "strength": "supports"
    },
    {
     "id": "atlassian",
     "name": "Atlassian",
     "sources": [
      "https://www.atlassian.com/blog/atlassian-engineering/unlocking-reliability-with-splunk-smartstore"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "strong",
   "url": "https://goodcodereview.com/production#prod-slo-error-budget",
   "markdown_url": "https://goodcodereview.com/production.md"
  },
  {
   "id": "svc-account-linking",
   "level": "service",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "authn",
    "accounts"
   ],
   "lang": "core",
   "applies_when": "The service links a federated identity to an existing account, or looks up an account by an email or profile claim.",
   "context": [
    "server"
   ],
   "tool_gate": null,
   "note": "the practical gate is an integration test: a local account with an unverified email, then an IdP sign-in asserting the same address, must not merge without proof of control of the local account",
   "lacks": null,
   "rule": "Link a federated identity to an existing account only after proving control of both, and key the link on the pair `iss` and `sub`, never on an unverified email or another profile claim.",
   "why": "An attacker who registers a victim's address first can wait for the victim to press \"Sign in with Google\" and inherit the account through an automatic merge. The same bug appears when an IdP asserts an address it never verified, or one that changed owner since it did. An authenticated session on the existing account, plus a freshly authenticated federated identity, proves both sides. `iss` and `sub` together are the only stable key; email, `preferred_username` and name can be reused or reassigned, so they are a lookup hint at best.",
   "counterpoint": "\"Users expect the Google button to find the account with their email.\" Find it by a verified address if you like, then stop and require the password or an authenticated session before linking. A verified flag is not always present, and an address verified at signup can belong to someone else today, so verify it yourself when the IdP does not. Treating the match as proof is what the attack needs. Google says that where it is authoritative for the address (a `@gmail.com` address, or `email_verified` with `hd` set) you may skip a password challenge. That settles who owns the address, not who controls the local account, so an IdP that is authoritative for the domain may be trusted for the lookup, and the merge still needs the authenticated local session when the local address was never verified.",
   "fail": {
    "language": "python",
    "code": "if user.email == claims[\"email\"]:\n    user.identities.append(claims[\"sub\"])     # merges on an unverified claim"
   },
   "pass": {
    "language": "python",
    "code": "if session.user is None:                         # control of the existing account\n    raise PermissionDenied\nif not (claims[\"iss\"] and claims[\"sub\"]):        # a stable federated key\n    raise PermissionDenied\nlink(session.user, key=(claims[\"iss\"], claims[\"sub\"]))\n# email stays a display field, never the lookup key"
   },
   "sources": [
    "https://www.microsoft.com/en-us/msrc/blog/2022/05/pre-hijacking-attacks",
    "https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html",
    "https://pages.nist.gov/800-63-4/sp800-63c/Federation/",
    "https://auth0.com/docs/manage-users/user-accounts/user-account-linking",
    "https://developers.google.com/identity/gsi/web/guides/verify-google-id-token",
    "https://openid.net/specs/openid-connect-core-1_0.html"
   ],
   "orgs": [
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://www.microsoft.com/en-us/msrc/blog/2022/05/pre-hijacking-attacks"
     ],
     "strength": "unjudged"
    },
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nist",
     "name": "NIST",
     "sources": [
      "https://pages.nist.gov/800-63-4/sp800-63c/Federation/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "auth0",
     "name": "Okta (Auth0)",
     "sources": [
      "https://auth0.com/docs/manage-users/user-accounts/user-account-linking"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://developers.google.com/identity/gsi/web/guides/verify-google-id-token"
     ],
     "strength": "unjudged"
    },
    {
     "id": "openid",
     "name": "OpenID Foundation",
     "sources": [
      "https://openid.net/specs/openid-connect-core-1_0.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-account-linking",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-api-cache-control",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "performance",
    "security"
   ],
   "topics": [
    "api",
    "http",
    "caching"
   ],
   "lang": "core",
   "applies_when": "A service returns responses that a browser, CDN, gateway or client library may cache.",
   "context": [
    "server"
   ],
   "tool_gate": "Integration test or header lint that rejects a 200 GET without `Cache-Control`.",
   "note": "do not let an edge rule override the origin header - PortSwigger, AWS, Google and Microsoft; decide cacheability by Content-Type - PortSwigger, Cloudflare and Google; \"the origin and the cache read the path the same way\" has PortSwigger only, so it is in Why",
   "lacks": null,
   "rule": "Set an explicit `Cache-Control` on every response a cache may store, use `private` or `no-store` for user-specific data, add `Vary` for any request header that changes the body, keep the origin's `Cache-Control` authoritative at the edge (no minimum TTL or force-cache override), and decide cacheability by the response `Content-Type`, not the URL extension.",
   "why": "With no directive, a cache may guess a lifetime from `Last-Modified`. A shared cache may then serve one user's response to another, or serve stale data for hours after a write. A dynamic page requested at `/account/x.css` is the same failure from the cache side: a rule that decides by extension stores a private page, and an edge rule that overrides `no-store` keeps it. The origin and the cache must also read the path the same way. When they do not, a path the origin treats as private can look like a static file to the cache.",
   "counterpoint": "\"Our CDN bypasses the cache for logged-in users.\" That config is a second place to get wrong, and it does not cover the browser. A cache rule that sets a minimum TTL, or a force-cache mode, can still beat the origin header. Check the specific policy value, and keep secrets out of cacheable URLs even when the header is right.",
   "fail": {
    "language": "text",
    "code": "GET /me/orders  -> 200\n(no Cache-Control, no Vary; body differs per Authorization)\n\nedge: GET /account/x.css -> stored for 1 hour\nedge: minimum TTL 3600 s, so origin Cache-Control: private, no-store is overridden"
   },
   "pass": {
    "language": "text",
    "code": "GET /me/orders  -> 200  Cache-Control: private, no-store\nGET /plans      -> 200  Cache-Control: public, max-age=300\n                         Vary: Accept-Language\n\nedge: minimum TTL 0, origin headers kept\nedge: /account/x.css not stored; Content-Type: text/html does not match .css"
   },
   "sources": [
    "https://www.rfc-editor.org/rfc/rfc9111.html",
    "https://opensource.zalando.com/restful-api-guidelines/",
    "https://cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html",
    "https://portswigger.net/web-security/web-cache-deception",
    "https://developers.cloudflare.com/cache/cache-security/cache-deception-armor/",
    "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html",
    "https://cloud.google.com/cdn/docs/caching",
    "https://learn.microsoft.com/en-us/azure/frontdoor/front-door-caching"
   ],
   "orgs": [
    {
     "id": "ietf",
     "name": "IETF",
     "sources": [
      "https://www.rfc-editor.org/rfc/rfc9111.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "zalando",
     "name": "Zalando",
     "sources": [
      "https://opensource.zalando.com/restful-api-guidelines/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html"
     ],
     "strength": "supports"
    },
    {
     "id": "portswigger",
     "name": "PortSwigger",
     "sources": [
      "https://portswigger.net/web-security/web-cache-deception"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cloudflare",
     "name": "Cloudflare",
     "sources": [
      "https://developers.cloudflare.com/cache/cache-security/cache-deception-armor/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://cloud.google.com/cdn/docs/caching"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/frontdoor/front-door-caching"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/service-server#svc-api-cache-control",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-api-conditional-writes",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "correctness"
   ],
   "topics": [
    "api",
    "http",
    "concurrency"
   ],
   "lang": "core",
   "applies_when": "Two callers can update the same resource through PUT, PATCH or DELETE.",
   "context": [
    "server"
   ],
   "tool_gate": "Integration test that sends a write with a stale `If-Match` and expects 412.",
   "note": null,
   "lacks": null,
   "rule": "Return an `ETag` on reads and honour `If-Match` on writes, answering 412 when the entity tag is stale.",
   "why": "A read-modify-write over HTTP is a race. The second writer overwrites the first without knowing it, and no error appears anywhere.",
   "counterpoint": "\"Last write wins is fine here.\" For a log or a counter it is; say so in the spec. For a document, a profile or a config that two people edit, it loses work. The card applies to shared resources that people edit.",
   "fail": {
    "language": "text",
    "code": "GET /docs/4  -> 200 {title: \"A\"}\nPUT /docs/4  {title: \"B\"}   -> 200   # overwrites a change made after the GET"
   },
   "pass": {
    "language": "text",
    "code": "GET /docs/4  -> 200  ETag: \"v7\"\nPUT /docs/4  If-Match: \"v7\"  -> 200  ETag: \"v8\"\nPUT /docs/4  If-Match: \"v7\"  -> 412 Precondition Failed"
   },
   "sources": [
    "https://www.rfc-editor.org/rfc/rfc9110.html",
    "https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md",
    "https://opensource.zalando.com/restful-api-guidelines/"
   ],
   "orgs": [
    {
     "id": "ietf",
     "name": "IETF",
     "sources": [
      "https://www.rfc-editor.org/rfc/rfc9110.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "zalando",
     "name": "Zalando",
     "sources": [
      "https://opensource.zalando.com/restful-api-guidelines/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-api-conditional-writes",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-api-contract-tests",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "compatibility",
    "testing"
   ],
   "topics": [
    "api",
    "contracts"
   ],
   "lang": "core",
   "applies_when": "A service calls, or is called by, another service owned by a different team or deployed on its own schedule.",
   "context": [
    "server"
   ],
   "tool_gate": "Contract test run in CI for both sides, and a gate that blocks deploy when a consumer's contract fails against the provider.",
   "note": "the Pact page read does not describe provider verification or the broker; those steps are not asserted here",
   "lacks": null,
   "rule": "Run a contract test for each cross-service call, derived from the consumer's real requests, and fail the provider's build when it breaks one.",
   "why": "A provider's own tests check what the provider thinks callers need. A spec check says what could happen, not what the consumer relies on. Only a check built from the consumer's usage catches the removed field before deploy, without a full-system end-to-end run.",
   "counterpoint": "\"End-to-end tests cover it.\" They are slow, need every service running, and run late. Contract tests run per service in minutes. Pact is one way; a consumer-written example set replayed against the provider also passes. For services deployed together from one repo, mark the card N/A.",
   "fail": {
    "language": "text",
    "code": "billing calls GET /customers/{id} and reads \"vat_id\"\ncustomers team removes \"vat_id\"; its tests pass; billing breaks in production"
   },
   "pass": {
    "language": "text",
    "code": "billing CI publishes a contract: GET /customers/{id} -> body has \"vat_id\"\ncustomers CI verifies every published contract and fails on the removal"
   },
   "sources": [
    "https://docs.pact.io/",
    "https://spec.openapis.org/oas/v3.1.0.html",
    "https://devblogs.microsoft.com/ise/pact-contract-testing-because-not-everything-needs-full-integration-tests",
    "https://en.paradigmadigital.com/dev/architecture-patterns-microservices-consumer-driven-contract-testing/",
    "https://www.thoughtworks.com/radar/techniques/consumer-driven-contract-testing"
   ],
   "orgs": [
    {
     "id": "pact",
     "name": "Pact",
     "sources": [
      "https://docs.pact.io/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "openapi",
     "name": "OpenAPI Initiative",
     "sources": [
      "https://spec.openapis.org/oas/v3.1.0.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://devblogs.microsoft.com/ise/pact-contract-testing-because-not-everything-needs-full-integration-tests"
     ],
     "strength": "supports"
    },
    {
     "id": "paradigma",
     "name": "Paradigma Digital",
     "sources": [
      "https://en.paradigmadigital.com/dev/architecture-patterns-microservices-consumer-driven-contract-testing/"
     ],
     "strength": "supports"
    },
    {
     "id": "thoughtworks",
     "name": "Thoughtworks",
     "sources": [
      "https://www.thoughtworks.com/radar/techniques/consumer-driven-contract-testing"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "strong",
   "url": "https://goodcodereview.com/service-server#svc-api-contract-tests",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-api-deprecation-sunset",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "compatibility"
   ],
   "topics": [
    "api",
    "deprecation"
   ],
   "lang": "core",
   "applies_when": "A service retires an endpoint, field, version or parameter that outside callers use.",
   "context": [
    "server"
   ],
   "tool_gate": "Middleware that adds both headers for routes tagged deprecated; a spec linter on `deprecated: true` without a sunset date; a dashboard counting calls that carry the headers.",
   "note": "Mark deprecated: Zalando, OpenAPI, Google AIP-185 (marks deprecation in the API definition, not literally an OpenAPI document). Send both headers: IETF, Zalando, GitHub (a vendor). Sunset not before Deprecation: IETF RFC 9745 only, so it is in Why",
   "lacks": null,
   "rule": "Before removing an endpoint, mark it `deprecated` in the spec and send `Deprecation` and `Sunset` headers.",
   "why": "The `Sunset` date must not be earlier than the `Deprecation` date (RFC 9745). Callers cannot read your roadmap. A removal with no signal in the responses is an outage for whoever has not read the changelog. Counting calls that carry the headers also tells you when removal is safe.",
   "counterpoint": "\"Our only callers are internal.\" Then the call count with the header gives you the list of owners to ask, and removal is cheap. [[chg-expand-contract]] covers the order of steps; this card covers what the response tells the caller. Both headers describe one resource, so send them on each deprecated route, not only on the API root.",
   "fail": {
    "language": "text",
    "code": "git: delete GET /v1/orders/{id}/summary\nresponses before removal: no header, spec not marked deprecated"
   },
   "pass": {
    "language": "text",
    "code": "GET /v1/orders/9/summary -> 200\n  Deprecation: @1767225600\n  Sunset: Wed, 01 Jul 2026 00:00:00 GMT\n  Link: <https://api.example.com/docs/summary-removal>; rel=\"deprecation\"\nspec: deprecated: true"
   },
   "sources": [
    "https://www.rfc-editor.org/rfc/rfc9745.html",
    "https://www.rfc-editor.org/rfc/rfc8594.html",
    "https://opensource.zalando.com/restful-api-guidelines/",
    "https://spec.openapis.org/oas/v3.1.0.html",
    "https://docs.github.com/en/rest/about-the-rest-api/api-versions",
    "https://google.aip.dev/185"
   ],
   "orgs": [
    {
     "id": "ietf",
     "name": "IETF",
     "sources": [
      "https://www.rfc-editor.org/rfc/rfc9745.html",
      "https://www.rfc-editor.org/rfc/rfc8594.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "zalando",
     "name": "Zalando",
     "sources": [
      "https://opensource.zalando.com/restful-api-guidelines/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "openapi",
     "name": "OpenAPI Initiative",
     "sources": [
      "https://spec.openapis.org/oas/v3.1.0.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://docs.github.com/en/rest/about-the-rest-api/api-versions"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://google.aip.dev/185"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-api-deprecation-sunset",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-api-documented-errors",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "correctness",
    "compatibility"
   ],
   "topics": [
    "api",
    "contracts",
    "errors"
   ],
   "lang": "core",
   "applies_when": "A service has an OpenAPI description, or callers depend on its error responses.",
   "context": [
    "server"
   ],
   "tool_gate": "Spec linter that requires 4xx and 5xx responses with a schema on every operation, and an example on each.",
   "note": "OpenAPI requires only one response per operation; the stricter rule comes from Zalando rule 151",
   "lacks": null,
   "rule": "Document every error status each operation can return, with a schema and one example.",
   "why": "Callers write error handling from the spec. An operation that lists only 200 teaches them nothing about 404, 409, 422 or 429, so they find out in production.",
   "counterpoint": "\"A shared `default` response covers everything.\" OpenAPI allows `default`, but it hides which codes carry which meaning. List the codes a caller must act on and use `default` for the rest.",
   "fail": {
    "language": "text",
    "code": "/orders/{id}:\n  get:\n    responses:\n      \"200\": {description: ok}"
   },
   "pass": {
    "language": "text",
    "code": "/orders/{id}:\n  get:\n    responses:\n      \"200\": {description: ok}\n      \"404\": {$ref: \"#/components/responses/Problem\"}   # has an example\n      \"429\": {$ref: \"#/components/responses/RateLimited\"}"
   },
   "sources": [
    "https://spec.openapis.org/oas/v3.1.0.html",
    "https://opensource.zalando.com/restful-api-guidelines/",
    "https://developer.siemens.com/guidelines/api-guidelines/rest/error.html"
   ],
   "orgs": [
    {
     "id": "openapi",
     "name": "OpenAPI Initiative",
     "sources": [
      "https://spec.openapis.org/oas/v3.1.0.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "zalando",
     "name": "Zalando",
     "sources": [
      "https://opensource.zalando.com/restful-api-guidelines/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "siemens",
     "name": "Siemens",
     "sources": [
      "https://developer.siemens.com/guidelines/api-guidelines/rest/error.html"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/service-server#svc-api-documented-errors",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-api-error-code-stable",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "compatibility"
   ],
   "topics": [
    "api",
    "errors"
   ],
   "lang": "core",
   "applies_when": "Callers of an HTTP or RPC API branch on the kind of error.",
   "context": [
    "server"
   ],
   "tool_gate": "Spec linter that requires an enumerated error `type` or `reason` per operation; a test that pins the codes.",
   "note": "RFC 9457 and Google AIP-193 describe the mechanism; the rule never to reword a published code is inference from both",
   "lacks": null,
   "rule": "Give each error a stable machine-readable code that callers branch on, and never make them parse the message text.",
   "why": "Message text gets reworded, translated and fixed. A client that matches on \"already exists\" breaks on the first edit. A published code is a contract.",
   "counterpoint": "\"The HTTP status is enough.\" It is too coarse: two different 409 conflicts need different handling. Add a code only where callers act differently; do not mint one for every branch.",
   "fail": {
    "language": "text",
    "code": "if \"already exists\" in response.json()[\"detail\"]:   # client\n    handle_conflict()"
   },
   "pass": {
    "language": "text",
    "code": "if response.json()[\"type\"] == \"https://api.example.com/problems/order-exists\":   # client\n    handle_conflict()\n# the server keeps that type value for as long as the API version lives"
   },
   "sources": [
    "https://www.rfc-editor.org/rfc/rfc9457.html",
    "https://google.aip.dev/193",
    "https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md"
   ],
   "orgs": [
    {
     "id": "ietf",
     "name": "IETF",
     "sources": [
      "https://www.rfc-editor.org/rfc/rfc9457.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://google.aip.dev/193"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-api-error-code-stable",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-api-key-scope",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "security"
   ],
   "topics": [
    "secrets",
    "api"
   ],
   "lang": "core",
   "applies_when": "The service issues an API key or long-lived access token to a caller.",
   "context": [
    "server"
   ],
   "tool_gate": null,
   "note": "exposure paths are covered by code-secret-scan, svc-no-secrets-in-url and sys-secrets-and-errors; this card covers what the issued key is allowed to do",
   "lacks": null,
   "rule": "Issue each API key with the narrowest scopes and a bounded lifetime, and rotate or revoke it after any exposure.",
   "why": "A key with account-wide scope turns one leaked string into the whole account, and a key with no end date is still valid years after the integration was forgotten. Scopes bound what a leak can do; an expiry or a rotation bounds how long it can do it. Rotating on a schedule, as Google and Stripe advise, also shows whether you can replace the key before an incident forces the question. Show the secret once, at creation, as AWS does.",
   "counterpoint": "\"Short expiry and rotation break integrations that nobody watches.\" That is the cost, and it is smaller than a key that never dies. Prefer a temporary credential where the platform offers one, give each integration its own key so rotation is one at a time, and keep the overlap window long enough for a deploy. A shared key with no owner cannot be rotated safely, which is the real defect.",
   "fail": {
    "language": "text",
    "code": "key = \"sk_live_...\"          # full account scope, no expiry\ndb.insert(user_id, key)      # plaintext, returned by every read"
   },
   "pass": {
    "language": "python",
    "code": "key = issue(user, scopes=[\"orders:read\"], expires_in=90 * DAY)\nreturn key                   # shown once\n# rotate on a schedule; revoke at once on exposure"
   },
   "sources": [
    "https://cloud.google.com/docs/authentication/api-keys",
    "https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens",
    "https://docs.github.com/en/rest/authentication/keeping-your-api-credentials-secure",
    "https://docs.stripe.com/keys-best-practices",
    "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html",
    "https://docs.gitlab.com/security/tokens/"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://cloud.google.com/docs/authentication/api-keys"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens",
      "https://docs.github.com/en/rest/authentication/keeping-your-api-credentials-secure"
     ],
     "strength": "unjudged"
    },
    {
     "id": "stripe",
     "name": "Stripe",
     "sources": [
      "https://docs.stripe.com/keys-best-practices"
     ],
     "strength": "unjudged"
    },
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://docs.gitlab.com/security/tokens/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-api-key-scope",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-api-naming-consistent",
   "level": "service",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "api",
    "naming"
   ],
   "lang": "core",
   "applies_when": "A service defines JSON fields, query parameters, headers or path segments for an HTTP API.",
   "context": [
    "server"
   ],
   "tool_gate": "Spec linter with a casing rule per location.",
   "note": "Zalando and Microsoft disagree on the convention, so the card checks consistency, not the choice",
   "lacks": null,
   "rule": "Use one casing convention per location (fields, query parameters, headers, paths) across the whole API, and state it in the API guide.",
   "why": "Mixed casing in one API makes callers look up each name. A client that guesses `orderId` after meeting `order_id` gets a silent null.",
   "counterpoint": "\"Pick the right convention.\" The sources disagree: Zalando asks for snake_case, Microsoft for camelCase in fields. Either passes. Do not change an existing API's casing to match a guide; that is a breaking change.",
   "fail": {
    "language": "text",
    "code": "GET /orders?pageSize=20 -> {\"order_id\": 1, \"createdAt\": \"...\", \"Total-Cents\": 500}"
   },
   "pass": {
    "language": "text",
    "code": "GET /orders?page_size=20 -> {\"order_id\": 1, \"created_at\": \"...\", \"total_cents\": 500}\n# snake_case everywhere, as docs/api-guide.md says"
   },
   "sources": [
    "https://opensource.zalando.com/restful-api-guidelines/",
    "https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md",
    "https://google.aip.dev/140",
    "https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md"
   ],
   "orgs": [
    {
     "id": "zalando",
     "name": "Zalando",
     "sources": [
      "https://opensource.zalando.com/restful-api-guidelines/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://google.aip.dev/140"
     ],
     "strength": "supports"
    },
    {
     "id": "kubernetes",
     "name": "Kubernetes",
     "sources": [
      "https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/service-server#svc-api-naming-consistent",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-api-problem-json",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "compatibility"
   ],
   "topics": [
    "api",
    "errors"
   ],
   "lang": "core",
   "applies_when": "A service returns errors from an HTTP API to other services or to clients.",
   "context": [
    "server"
   ],
   "tool_gate": "Middleware that builds every error body from one problem-details type; a test that fails on an error response without the media type.",
   "note": null,
   "lacks": null,
   "rule": "Return errors as `application/problem+json` with a `status` that equals the HTTP status, and leave stack traces and internal detail out.",
   "why": "Callers then parse every error one way. A body that differs per endpoint, or a `status` that disagrees with the real code, makes generic client and proxy handling unreliable. A stack trace in `detail` hands an attacker the implementation.",
   "counterpoint": "\"We already have our own error envelope.\" That is fine inside one team's API, if it is one envelope and documented. The card asks for one shape everywhere; problem details is the one with a registered media type and existing client support. See [[sys-secrets-and-errors]] for what must stay out of any error.",
   "fail": {
    "language": "text",
    "code": "HTTP/1.1 500\nContent-Type: text/html\n\n<pre>NullPointerException at OrderRepo.java:88 ...</pre>"
   },
   "pass": {
    "language": "text",
    "code": "HTTP/1.1 404\nContent-Type: application/problem+json\n\n{\"type\": \"https://api.example.com/problems/order-not-found\",\n \"title\": \"Order not found\", \"status\": 404,\n \"detail\": \"No order with id 7731 for this account.\"}"
   },
   "sources": [
    "https://www.rfc-editor.org/rfc/rfc9457.html",
    "https://opensource.zalando.com/restful-api-guidelines/",
    "https://ukhsa-collaboration.github.io/standards-org/api-design-guidelines/api-guidelines/error-handling/"
   ],
   "orgs": [
    {
     "id": "ietf",
     "name": "IETF",
     "sources": [
      "https://www.rfc-editor.org/rfc/rfc9457.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "zalando",
     "name": "Zalando",
     "sources": [
      "https://opensource.zalando.com/restful-api-guidelines/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "ukhsa",
     "name": "UKHSA",
     "sources": [
      "https://ukhsa-collaboration.github.io/standards-org/api-design-guidelines/api-guidelines/error-handling/"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/service-server#svc-api-problem-json",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-api-retry-after",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "reliability"
   ],
   "topics": [
    "api",
    "http",
    "resilience"
   ],
   "lang": "core",
   "applies_when": "A service answers 429 or 503 to HTTP callers.",
   "context": [
    "server"
   ],
   "tool_gate": "Integration test that forces a 429 and a 503 and asserts a `Retry-After` header.",
   "note": "the draft rate-limit header fields are not required here; RFC 6585 on 429 was not fetched",
   "lacks": null,
   "rule": "Send `Retry-After` on every 429 and 503, and set it no earlier than the time capacity returns.",
   "why": "Without it, every client invents its own backoff, and a hundred of them retry in the same second. The header lets the server pace its own recovery.",
   "counterpoint": "\"Clients should use exponential backoff anyway.\" They should, and [[svc-retry-policy]] says so. A server hint narrows the guess; it does not replace the client's cap. The IETF `RateLimit` header fields are still an Internet-Draft, so the card requires only `Retry-After`, which is in the HTTP standard. The quota itself is [[svc-rate-limit-quota]].",
   "fail": {
    "language": "text",
    "code": "HTTP/1.1 429 Too Many Requests\n\n{\"error\": \"slow down\"}"
   },
   "pass": {
    "language": "text",
    "code": "HTTP/1.1 429 Too Many Requests\nRetry-After: 30\nContent-Type: application/problem+json"
   },
   "sources": [
    "https://www.rfc-editor.org/rfc/rfc9110.html",
    "https://opensource.zalando.com/restful-api-guidelines/",
    "https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/",
    "https://learn.microsoft.com/en-us/azure/architecture/patterns/throttling",
    "https://shopify.dev/docs/api/admin-rest/usage/rate-limits",
    "https://docs.slack.dev/apis/web-api/rate-limits"
   ],
   "orgs": [
    {
     "id": "ietf",
     "name": "IETF",
     "sources": [
      "https://www.rfc-editor.org/rfc/rfc9110.html",
      "https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "zalando",
     "name": "Zalando",
     "sources": [
      "https://opensource.zalando.com/restful-api-guidelines/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/architecture/patterns/throttling"
     ],
     "strength": "supports"
    },
    {
     "id": "shopify",
     "name": "Shopify",
     "sources": [
      "https://shopify.dev/docs/api/admin-rest/usage/rate-limits"
     ],
     "strength": "supports"
    },
    {
     "id": "slack",
     "name": "Slack",
     "sources": [
      "https://docs.slack.dev/apis/web-api/rate-limits"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "strong",
   "url": "https://goodcodereview.com/service-server#svc-api-retry-after",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-api-specific-status-codes",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "compatibility"
   ],
   "topics": [
    "api",
    "http"
   ],
   "lang": "core",
   "applies_when": "A service returns HTTP responses that callers or intermediaries interpret by status code.",
   "context": [
    "server"
   ],
   "tool_gate": "Integration tests asserting status and headers per operation; a spec linter for 201 and 405 responses.",
   "note": "RFC 9110 sections 9.3.4, 10.2.1 and 15.3.2; section 15.5.6 on 405 was not read",
   "lacks": null,
   "rule": "Return the most specific status code that fits: 201 with `Location` for a create, 405 with `Allow` for a wrong method, and a 4xx or 5xx for any failure.",
   "why": "Clients, proxies and monitors act on the status line. A 200 with an error in the body hides failures from retries, caches and error-rate alerts. A create that returns 200 without `Location` forces the caller to guess the new URL.",
   "counterpoint": "\"A 200 with a status field is easier for our clients.\" It breaks every generic tool that reads the status line, including your own dashboards. Choosing between two fitting codes is a style call; the card fails only a code that says the wrong thing.",
   "fail": {
    "language": "text",
    "code": "POST /orders -> 200 {\"error\": \"out of stock\"}\nPATCH /orders (route is read-only) -> 404, no Allow header"
   },
   "pass": {
    "language": "text",
    "code": "POST /orders -> 201  Location: /orders/9031\nPOST /orders (out of stock) -> 409 application/problem+json\nPATCH /orders (not supported) -> 405  Allow: GET, POST"
   },
   "sources": [
    "https://www.rfc-editor.org/rfc/rfc9110.html",
    "https://opensource.zalando.com/restful-api-guidelines/",
    "https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-implementation"
   ],
   "orgs": [
    {
     "id": "ietf",
     "name": "IETF",
     "sources": [
      "https://www.rfc-editor.org/rfc/rfc9110.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "zalando",
     "name": "Zalando",
     "sources": [
      "https://opensource.zalando.com/restful-api-guidelines/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-implementation"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/service-server#svc-api-specific-status-codes",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-api-versioning-policy",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "compatibility"
   ],
   "topics": [
    "api",
    "versioning"
   ],
   "lang": "core",
   "applies_when": "A service offers an HTTP API to callers you do not deploy together with it.",
   "context": [
    "server"
   ],
   "tool_gate": "Spec diff in CI that fails on a breaking change inside a published version.",
   "note": "the three sources pick three different mechanisms; the card requires only that one is chosen and documented",
   "lacks": null,
   "rule": "Write down one versioning scheme for the API, and ship no breaking change inside a version it has published.",
   "why": "Callers upgrade on their own schedule. With no stated scheme, each breaking change is a surprise, and each team invents its own v2.",
   "counterpoint": "\"We will cross that bridge later.\" Later arrives with a customer on the old shape. The scheme can be minimal. A media type, a dated query parameter and a pinned header all work. Pick one and write it down. What counts as breaking is in [[chg-breaking-change-taxonomy]].",
   "fail": {
    "language": "text",
    "code": "GET /orders   # renamed total -> amount_total\nno version marker anywhere; README says \"we try not to break things\""
   },
   "pass": {
    "language": "text",
    "code": "docs/api-versioning.md: dated api-version query parameter, previews last 6 months, 90 days' notice\nGET /orders?api-version=2026-03-01 keeps returning \"total\"\nthe rename ships under api-version=2026-09-01"
   },
   "sources": [
    "https://opensource.zalando.com/restful-api-guidelines/",
    "https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md",
    "https://docs.stripe.com/api/versioning"
   ],
   "orgs": [
    {
     "id": "zalando",
     "name": "Zalando",
     "sources": [
      "https://opensource.zalando.com/restful-api-guidelines/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "stripe",
     "name": "Stripe",
     "sources": [
      "https://docs.stripe.com/api/versioning"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-api-versioning-policy",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-async-content-reserves-space",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "usability",
    "performance"
   ],
   "topics": [
    "frontend",
    "css"
   ],
   "lang": "core",
   "applies_when": "A page loads images, embeds, fonts, ads, or fetched content after the first paint.",
   "context": [
    "ui"
   ],
   "tool_gate": "Lighthouse CI assertion on `cumulative-layout-shift`.",
   "note": "injected content and ads are backed by Google only; cut to image dimensions the Rule has Google and Mozilla firm, while WHATWG advises width and height only on lazy-loaded images (checked 2026-10-24), so it is a partial third; Apple and NN/g on placeholders are indirect",
   "lacks": null,
   "rule": "Reserve the space for late-loading content with set dimensions or an aspect ratio, so nothing already on screen moves.",
   "why": "A page that jumps while someone reads or taps makes them hit the wrong target. A cumulative layout shift of 0.1 or less is good, and over 0.25 is poor, measured on real users at the 75th percentile.",
   "counterpoint": "\"Ads vary in height.\" Reserve the common height, or the largest, and accept some blank space. A banner that pushes the article down is the worse trade.",
   "fail": {
    "language": "html",
    "code": "<img src=\"/hero.jpg\">\n<div id=\"promo\"></div>   <!-- script inserts a banner above the article -->"
   },
   "pass": {
    "language": "html",
    "code": "<img src=\"/hero.jpg\" width=\"1200\" height=\"630\" style=\"max-width: 100%; height: auto\">\n<div id=\"promo\" style=\"min-height: 90px\"></div>"
   },
   "sources": [
    "https://web.dev/articles/cls",
    "https://developer.apple.com/tutorials/data/design/human-interface-guidelines/loading.json",
    "https://www.nngroup.com/articles/skeleton-screens/",
    "https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/img",
    "https://developer.mozilla.org/en-US/docs/Glossary/CLS",
    "https://html.spec.whatwg.org/multipage/embedded-content.html",
    "https://shopify.dev/docs/storefronts/themes/best-practices/performance/prevent-image-layout-shift",
    "https://www.cloudflare.com/learning/performance/what-are-core-web-vitals/"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://web.dev/articles/cls"
     ],
     "strength": "unjudged"
    },
    {
     "id": "apple",
     "name": "Apple",
     "sources": [
      "https://developer.apple.com/tutorials/data/design/human-interface-guidelines/loading.json"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nngroup",
     "name": "Nielsen Norman Group",
     "sources": [
      "https://www.nngroup.com/articles/skeleton-screens/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/img",
      "https://developer.mozilla.org/en-US/docs/Glossary/CLS"
     ],
     "strength": "unjudged"
    },
    {
     "id": "whatwg",
     "name": "WHATWG",
     "sources": [
      "https://html.spec.whatwg.org/multipage/embedded-content.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "shopify",
     "name": "Shopify",
     "sources": [
      "https://shopify.dev/docs/storefronts/themes/best-practices/performance/prevent-image-layout-shift"
     ],
     "strength": "supports"
    },
    {
     "id": "cloudflare",
     "name": "Cloudflare",
     "sources": [
      "https://www.cloudflare.com/learning/performance/what-are-core-web-vitals/"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/service-ui#svc-async-content-reserves-space",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-auth-throttle-generic-errors",
   "level": "service",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "authn",
    "rate-limit"
   ],
   "lang": "core",
   "applies_when": "The service has a login, password-reset or one-time-code endpoint.",
   "context": [
    "server"
   ],
   "tool_gate": "Rate-limit middleware on auth routes; test that known and unknown users get identical responses.",
   "note": null,
   "lacks": null,
   "rule": "Limit failed attempts per account, and return the same message and status code whether or not the account exists.",
   "why": "Unlimited guesses make passwords and 6-digit codes brute-forceable. Different errors for \"no such user\" and \"wrong password\" let an attacker list valid accounts first. Limit per account, since a per-IP limit is bypassed by rotating addresses. Response time leaks the same fact: if the code hashes the password only when the user exists, a fast reply means \"no such user\", so hash on every attempt. Registration needs its own design: give the same reply whether or not the address is taken, and email the owner of an existing account instead of creating a second one.",
   "counterpoint": "\"A lockout lets attackers lock out real users.\" Use a rising delay, not a permanent lock, and keep the recovery flow reachable. Log every lockout so abuse shows up.",
   "fail": {
    "language": "text",
    "code": "if not user: return \"No such user\", 404\nif not check(pw): return \"Wrong password\", 401      # no attempt limit"
   },
   "pass": {
    "language": "text",
    "code": "if too_many_failures(account_key): return \"Try again later\", 429   # checked before the password\nok = check(pw, user.pw_hash if user else DUMMY_HASH)              # hash runs either way\nif not (user and ok): record_failure(account_key); return \"Invalid credentials\", 401"
   },
   "sources": [
    "https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html",
    "https://api-security.owasp.org/editions/2023/en/0xa4-unrestricted-resource-consumption",
    "https://pages.nist.gov/800-63-4/sp800-63b.html",
    "https://portswigger.net/web-security/authentication/password-based",
    "https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-228-upd1.pdf",
    "https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity-configuration",
    "https://auth0.com/docs/secure/attack-protection/brute-force-protection",
    "https://docs.cloud.google.com/identity-platform/docs/admin/email-enumeration-protection"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html",
      "https://api-security.owasp.org/editions/2023/en/0xa4-unrestricted-resource-consumption"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nist",
     "name": "NIST",
     "sources": [
      "https://pages.nist.gov/800-63-4/sp800-63b.html",
      "https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-228-upd1.pdf"
     ],
     "strength": "unjudged"
    },
    {
     "id": "portswigger",
     "name": "PortSwigger",
     "sources": [
      "https://portswigger.net/web-security/authentication/password-based"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity-configuration"
     ],
     "strength": "unjudged"
    },
    {
     "id": "auth0",
     "name": "Okta (Auth0)",
     "sources": [
      "https://auth0.com/docs/secure/attack-protection/brute-force-protection"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://docs.cloud.google.com/identity-platform/docs/admin/email-enumeration-protection"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-auth-throttle-generic-errors",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-authn-by-default",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "security"
   ],
   "topics": [
    "authn"
   ],
   "lang": "core",
   "applies_when": "The service has routes that need a signed-in user next to routes that are public.",
   "context": [
    "server"
   ],
   "tool_gate": "A framework fallback that requires authentication (ASP.NET Core `FallbackPolicy`, Django `LoginRequiredMiddleware`, Spring Security `anyRequest().authenticated()`); a test that calls every route without credentials and expects 401 or a login redirect unless the route is on the public list.",
   "note": "svc-authz-central-deny-default decides what a known user may do; this card decides whether an anonymous caller gets in at all (CWE-306)",
   "lacks": null,
   "rule": "Require authentication on every route by default, and mark each public route by name.",
   "why": "When each handler opts in to authentication, the one that forgets is open to the internet, and nothing in the diff looks wrong. A global fallback flips the default: a new route is closed until someone marks it public, and that mark is a line a reviewer can question. Health checks and internal admin ports count as routes too.",
   "counterpoint": "\"Our gateway already checks the token.\" Then the service trusts every caller that can reach it without the gateway: another pod, a debug port, a misrouted path. Keep the gateway check and the fallback in the service. The fallback costs one middleware.",
   "fail": {
    "language": "python",
    "code": "@app.get(\"/invoices/<id>\")          # no login check; anonymous callers get the invoice\ndef invoice(id): ..."
   },
   "pass": {
    "language": "python",
    "code": "PUBLIC = {\"login\", \"health\", \"static\"}\n\n@app.before_request\ndef require_login():\n    if request.endpoint not in PUBLIC and not current_user.is_authenticated:\n        abort(401)"
   },
   "sources": [
    "https://cwe.mitre.org/data/definitions/306.html",
    "https://learn.microsoft.com/en-us/aspnet/core/security/authorization/secure-data",
    "https://docs.djangoproject.com/en/5.2/ref/middleware/",
    "https://docs.spring.io/spring-security/reference/servlet/authorization/authorize-http-requests.html"
   ],
   "orgs": [
    {
     "id": "mitre",
     "name": "MITRE",
     "sources": [
      "https://cwe.mitre.org/data/definitions/306.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/aspnet/core/security/authorization/secure-data"
     ],
     "strength": "unjudged"
    },
    {
     "id": "django",
     "name": "Django Software Foundation",
     "sources": [
      "https://docs.djangoproject.com/en/5.2/ref/middleware/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "spring",
     "name": "Spring",
     "sources": [
      "https://docs.spring.io/spring-security/reference/servlet/authorization/authorize-http-requests.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-authn-by-default",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-authz-central-deny-default",
   "level": "service",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "authz"
   ],
   "lang": "core",
   "applies_when": "The service exposes routes or functions that different roles may call.",
   "context": [
    "server"
   ],
   "tool_gate": "Router that denies routes with no policy; a test that lists every route and its required role.",
   "note": null,
   "lacks": null,
   "rule": "Route every request through one authorization layer that denies when no rule allows it.",
   "why": "Per-handler checks drift: one admin route ships without its decorator and any user can call it. A central layer with deny as the fallback turns a forgotten check into a 403 instead of a hole.",
   "counterpoint": "\"A central policy is a bottleneck for feature teams.\" Keep the engine central and let each team own its rows. The cost of a one-line policy entry is small next to an unguarded admin route.",
   "fail": {
    "language": "text",
    "code": "@app.route(\"/admin/export\")        # no role check; framework default is allow\ndef export(): ..."
   },
   "pass": {
    "language": "text",
    "code": "policy = {\"export\": {\"admin\"}}             # endpoint -> roles, reviewed in one place\n@app.before_request\ndef gate(): require_role(policy.get(request.endpoint))   # missing entry -> 403"
   },
   "sources": [
    "https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html",
    "https://api-security.owasp.org/editions/2023/en/0xa5-broken-function-level-authorization",
    "https://top10.owasp.org/2025/A01_2025-Broken_Access_Control",
    "https://portswigger.net/web-security/access-control",
    "https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_evaluation-logic_policy-eval-denyallow.md",
    "https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-228-upd1.pdf"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html",
      "https://api-security.owasp.org/editions/2023/en/0xa5-broken-function-level-authorization",
      "https://top10.owasp.org/2025/A01_2025-Broken_Access_Control"
     ],
     "strength": "unjudged"
    },
    {
     "id": "portswigger",
     "name": "PortSwigger",
     "sources": [
      "https://portswigger.net/web-security/access-control"
     ],
     "strength": "unjudged"
    },
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_evaluation-logic_policy-eval-denyallow.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nist",
     "name": "NIST",
     "sources": [
      "https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-228-upd1.pdf"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-authz-central-deny-default",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-best-effort-optional-deps",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "reliability",
    "observability"
   ],
   "topics": [
    "resilience",
    "dependencies"
   ],
   "lang": "core",
   "applies_when": "The critical path of a request or a job calls a dependency whose data the product can do without, such as enrichment, recommendations, analytics, or a rate-limit control plane.",
   "context": [],
   "tool_gate": null,
   "note": "best-effort calls - Netflix (calls to metrics, logs and other systems are best-effort; a slow dependency degrades the graph and never blocks it), Uber (rate-limit clients fail open when the control plane is down), Cabify (skips non-critical details in a driver offer when it cannot collect them fast enough); a metric and alert on each fallback - Cabify only",
   "lacks": null,
   "rule": "Give each call to a non-critical dependency a short budget and a default, so its failure degrades the result instead of failing the critical path, and count every fallback.",
   "why": "An order page should not fail because the recommendations service is slow. Netflix makes enrichment best-effort, so a slow dependency can degrade its knowledge graph but never block it. Uber's rate-limit clients let traffic through when the control plane is down, because blocking would cause an outage of its own. Cabify drops non-critical details from a driver offer when it cannot collect them in time. It also puts metrics and alerts on every such fallback, because SLO dashboards built for critical flows miss them.",
   "counterpoint": "\"A silent default hides an outage.\" It does when nobody counts it, so the PASS needs the metric and an alert on it. Call a dependency non-critical only when the product works without its data. Authentication, payment, and authorization checks are critical, and they fail closed. Failing open on a rate limiter gives up protection to keep availability, so write that choice into the design. For the timeout itself, see [[svc-timeouts]]. To check that the fallback really runs, see [[prod-kill-switch-fallback]].",
   "fail": {
    "language": "python",
    "code": "def trip_offer(trip_id):\n    trip = trips.get(trip_id, timeout=0.5)\n    eta = traffic.eta(trip.route, timeout=2.0)   # optional detail, but an error here fails the offer\n    return Offer(trip, eta)"
   },
   "pass": {
    "language": "python",
    "code": "def trip_offer(trip_id):\n    trip = trips.get(trip_id, timeout=0.5)\n    try:\n        eta = traffic.eta(trip.route, timeout=0.1)\n    except (TimeoutError, ConnectionError):\n        eta = None                                # the offer goes out without the ETA\n        degraded.labels(dep=\"traffic\").inc()      # alert on this rate\n    return Offer(trip, eta)"
   },
   "sources": [
    "https://netflixtechblog.com/ontology-driven-observability-building-an-end-to-end-knowledge-graph-at-netflix-scale-421392cf5adf",
    "https://www.uber.com/es/en/blog/ubers-rate-limiting-system/",
    "https://tech.cabify.com/blog/engineering/degraded-experiences",
    "https://engineering.atspotify.com/2013/03/achieving-fault-tolerance-with-intelligent-daemons",
    "https://engineering.zalando.com/posts/2022/04/operation-based-slos.html"
   ],
   "orgs": [
    {
     "id": "netflix",
     "name": "Netflix",
     "sources": [
      "https://netflixtechblog.com/ontology-driven-observability-building-an-end-to-end-knowledge-graph-at-netflix-scale-421392cf5adf"
     ],
     "strength": "unjudged"
    },
    {
     "id": "uber",
     "name": "Uber",
     "sources": [
      "https://www.uber.com/es/en/blog/ubers-rate-limiting-system/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cabify",
     "name": "Cabify",
     "sources": [
      "https://tech.cabify.com/blog/engineering/degraded-experiences"
     ],
     "strength": "unjudged"
    },
    {
     "id": "spotify",
     "name": "Spotify",
     "sources": [
      "https://engineering.atspotify.com/2013/03/achieving-fault-tolerance-with-intelligent-daemons"
     ],
     "strength": "supports"
    },
    {
     "id": "zalando",
     "name": "Zalando",
     "sources": [
      "https://engineering.zalando.com/posts/2022/04/operation-based-slos.html"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/service-any#svc-best-effort-optional-deps",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-bound-client-controlled-sizes",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "security"
   ],
   "topics": [
    "dos",
    "api"
   ],
   "lang": "core",
   "applies_when": "A request parameter or body sets how many records, bytes, items or operations the server handles.",
   "context": [
    "server"
   ],
   "tool_gate": "Gateway request-size limit; schema `maxLength`/`maxItems`; test with a 10 MB body and `limit=1000000`.",
   "note": null,
   "lacks": null,
   "rule": "Cap every client-controlled size and count on the server: body bytes, string length, array items, page size and batch size.",
   "why": "A request for `limit=10000000` or a batch of 50,000 operations lets one client exhaust memory or the bill for a paid backend. The cap has to sit in the server, since a UI limit is not one. [[svc-rate-limit-quota]] covers request rate; this card covers size.",
   "counterpoint": "\"Legit clients sometimes need big batches.\" Offer a documented bulk or export endpoint with its own quota. The default path stays capped.",
   "fail": {
    "language": "text",
    "code": "limit = int(request.args[\"limit\"])\nrows = db.fetch(limit)                              # no ceiling\nitems: list[Item]                                    # schema with no maxItems"
   },
   "pass": {
    "language": "text",
    "code": "limit = max(1, min(int(request.args.get(\"limit\", 50)), 100))\nitems: list[Item] = Field(max_length=100)            # body capped at 1 MB by the proxy"
   },
   "sources": [
    "https://api-security.owasp.org/editions/2023/en/0xa4-unrestricted-resource-consumption",
    "https://genai.owasp.org/llmrisk/llm102025-unbounded-consumption/",
    "https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-228-upd1.pdf",
    "https://pkg.go.dev/net/http#MaxBytesReader",
    "https://docs.djangoproject.com/en/5.2/ref/settings/#data-upload-max-memory-size"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://api-security.owasp.org/editions/2023/en/0xa4-unrestricted-resource-consumption",
      "https://genai.owasp.org/llmrisk/llm102025-unbounded-consumption/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nist",
     "name": "NIST",
     "sources": [
      "https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-228-upd1.pdf"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://pkg.go.dev/net/http#MaxBytesReader"
     ],
     "strength": "unjudged"
    },
    {
     "id": "django",
     "name": "Django Software Foundation",
     "sources": [
      "https://docs.djangoproject.com/en/5.2/ref/settings/#data-upload-max-memory-size"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-bound-client-controlled-sizes",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-bounded-background-tasks",
   "level": "service",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "reliability",
    "correctness"
   ],
   "topics": [
    "concurrency",
    "resilience",
    "async"
   ],
   "lang": "core",
   "applies_when": "The code starts a task, goroutine, or thread per item, request, or message.",
   "context": [],
   "tool_gate": null,
   "note": "Both clauses are backed by Google (the Go blog, flagged as a vendor), Cabify and AWS (a vendor). AWS states a semaphore and per-workload thread pools that limit concurrency, and its page names the same thread-pool exhaustion the Why describes. errgroup and asyncio supply the limiting tools",
   "lacks": null,
   "rule": "Cap how many background tasks run at once, and never spawn one per input with no limit.",
   "why": "Input size is not under your control. One task per item works for ten items and exhausts memory, connections, or the downstream service at ten thousand, and the failure lands on everything else in the process. Go's `errgroup.SetLimit`, asyncio's `Semaphore` and Tokio's `Semaphore` exist for exactly this.",
   "counterpoint": "\"Tasks are cheap.\" Creating them is, but what each one holds (a socket, a buffer, a database connection) is not. A semaphore around each handler also caps concurrency, though it still creates one task object per input. This card covers work that a service starts for itself. [[sys-load-shedding-bounded-queues]] covers refusing incoming requests under load.",
   "fail": {
    "language": "python",
    "code": "for msg in batch:                       # batch size comes from the queue\n    asyncio.create_task(handle(msg))    # 50k messages -> 50k concurrent handlers"
   },
   "pass": {
    "language": "python",
    "code": "async def worker(queue):\n    while (msg := await queue.get()) is not None:\n        await handle(msg)\n\nqueue = asyncio.Queue(maxsize=100)\nasync with asyncio.TaskGroup() as tg:\n    for _ in range(32):                 # at most 32 handlers at once\n        tg.create_task(worker(queue))\n    for msg in batch:\n        await queue.put(msg)\n    for _ in range(32):\n        await queue.put(None)           # one stop signal per worker"
   },
   "sources": [
    "https://pkg.go.dev/golang.org/x/sync/errgroup",
    "https://go.dev/blog/pipelines",
    "https://docs.python.org/3/library/asyncio-sync.html",
    "https://docs.rs/tokio/latest/tokio/sync/struct.Semaphore.html",
    "https://tech.cabify.com/blog/engineering/shadow-routing-providers",
    "https://aws.amazon.com/builders-library/avoiding-insurmountable-queue-backlogs/"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://pkg.go.dev/golang.org/x/sync/errgroup",
      "https://go.dev/blog/pipelines"
     ],
     "strength": "unjudged"
    },
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://docs.python.org/3/library/asyncio-sync.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "tokio",
     "name": "Tokio project",
     "sources": [
      "https://docs.rs/tokio/latest/tokio/sync/struct.Semaphore.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cabify",
     "name": "Cabify",
     "sources": [
      "https://tech.cabify.com/blog/engineering/shadow-routing-providers"
     ],
     "strength": "unjudged"
    },
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://aws.amazon.com/builders-library/avoiding-insurmountable-queue-backlogs/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-any#svc-bounded-background-tasks",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-cache-key-completeness",
   "level": "service",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "http",
    "caching"
   ],
   "lang": "core",
   "applies_when": "A shared cache (CDN, reverse proxy, gateway, or framework cache) stores responses the service generates.",
   "context": [
    "server"
   ],
   "tool_gate": "A test that sends an unkeyed header or a GET body and asserts the response body does not change.",
   "note": "svc-api-cache-control owns Cache-Control and Vary; the key and the unkeyed inputs are this card. key completeness - AWS, Cloudflare and Fastly (the Microsoft Front Door page covers only query-string keying, so it is not a source); unkeyed input kept out of the body - Cloudflare, PortSwigger and Fastly; static-only caching has Cloudflare, PortSwigger and Google but vendors disagree, so it is in Why",
   "lacks": null,
   "rule": "Key every request input that can change the response, and strip any other request value before it reaches a cached body.",
   "why": "A cache keeps one response per key. If the origin echoes a header, a cookie, or a GET body that the key ignores, the first request stores content that every later visitor receives. An attacker can then place content on your domain without touching the origin: a link or a form. A value that reaches the body belongs in the key, or it must not reach the body at all. Caching only static types by default narrows the surface, though CDN defaults differ.",
   "counterpoint": "\"We leave that header out of the key to keep the hit rate up.\" It does raise the hit rate, by serving one requester's variant to everyone. Strip or rewrite the value when the response does not need it, and key it when it does.",
   "fail": {
    "language": "text",
    "code": "GET /home HTTP/1.1\nX-Forwarded-Host: attacker.example\n\norigin body: <a href=\"https://attacker.example/account\">Account</a>\ncache key: path only"
   },
   "pass": {
    "language": "text",
    "code": "edge: strip X-Forwarded-Host, X-Original-URL and X-Rewrite-URL before the origin\nedge: return 400 to a GET with a body, or bypass the cache for it\norigin: echo a header only when it is in Vary or the cache key\nCache-Control: public, max-age=60   Vary: Accept-Language"
   },
   "sources": [
    "https://portswigger.net/web-security/web-cache-poisoning",
    "https://developers.cloudflare.com/cache/cache-security/avoid-web-poisoning/",
    "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/understanding-the-cache-key.html",
    "https://www.fastly.com/documentation/guides/full-site-delivery/caching/preventing-cache-poisoning/",
    "https://www.fastly.com/documentation/guides/full-site-delivery/caching/manipulating-the-cache-key/",
    "https://cloud.google.com/cdn/docs/caching"
   ],
   "orgs": [
    {
     "id": "portswigger",
     "name": "PortSwigger",
     "sources": [
      "https://portswigger.net/web-security/web-cache-poisoning"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cloudflare",
     "name": "Cloudflare",
     "sources": [
      "https://developers.cloudflare.com/cache/cache-security/avoid-web-poisoning/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/understanding-the-cache-key.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "fastly",
     "name": "Fastly",
     "sources": [
      "https://www.fastly.com/documentation/guides/full-site-delivery/caching/preventing-cache-poisoning/",
      "https://www.fastly.com/documentation/guides/full-site-delivery/caching/manipulating-the-cache-key/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://cloud.google.com/cdn/docs/caching"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-cache-key-completeness",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-circuit-breaker-bulkhead",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "reliability"
   ],
   "topics": [
    "resilience",
    "isolation"
   ],
   "lang": "core",
   "applies_when": "A service calls several dependencies from shared pools, or retries a dependency that may be down.",
   "context": [],
   "tool_gate": null,
   "note": null,
   "lacks": null,
   "rule": "Give each critical dependency its own concurrency limit.",
   "why": "One shared pool lets a slow dependency use every thread and starve healthy paths. Retrying a dead dependency only adds load, so also fail fast through a breaker with limited half-open probing where retries are demonstrably harmful.",
   "counterpoint": "\"More moving parts than a timeout.\" True. Start with the timeout and the per-dependency limit, and add the breaker where retries are demonstrably harmful. Retry and breaker have opposite aims, so do not present one as a substitute for the other.",
   "fail": {
    "language": "text",
    "code": "pool = ThreadPool(50)               # used by billing, search, email alike\nbreaker = None                      # retries a dead host every request"
   },
   "pass": {
    "language": "text",
    "code": "pools = {\"billing\": asyncio.Semaphore(10), \"search\": asyncio.Semaphore(20)}\n# pseudocode: breaker logs and counts every state change; half-open allows 1 probe"
   },
   "sources": [
    "https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker",
    "https://learn.microsoft.com/en-us/azure/architecture/patterns/bulkhead",
    "https://aws.amazon.com/builders-library/dependency-isolation",
    "https://netflixtechblog.com/fault-tolerance-in-a-high-volume-distributed-system-91ab4faae74a"
   ],
   "orgs": [
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker",
      "https://learn.microsoft.com/en-us/azure/architecture/patterns/bulkhead"
     ],
     "strength": "unjudged"
    },
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://aws.amazon.com/builders-library/dependency-isolation"
     ],
     "strength": "supports"
    },
    {
     "id": "netflix",
     "name": "Netflix",
     "sources": [
      "https://netflixtechblog.com/fault-tolerance-in-a-high-volume-distributed-system-91ab4faae74a"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/service-any#svc-circuit-breaker-bulkhead",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-cli-destructive-dry-run",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "reliability",
    "maintainability"
   ],
   "topics": [
    "cli",
    "safety"
   ],
   "lang": "core",
   "applies_when": "A command-line tool has a command that deletes, overwrites or sends something.",
   "context": [
    "cli"
   ],
   "tool_gate": "CLI test that runs the command with the preview flag and checks nothing changed.",
   "note": "Preview flag backed by Kubernetes (kubectl --dry-run), Git (git clean -n) and GNU (make -n); confirmation bypass backed by GitHub (gh repo delete --yes), Microsoft (az --yes), Google (gcloud --quiet) and HashiCorp (terraform -auto-approve)",
   "lacks": null,
   "rule": "Give a destructive command a preview flag that changes nothing and a flag that skips the confirmation prompt.",
   "why": "Without a preview, the first run is the real one. Without a bypass, a script cannot run the command at all, so people write wrappers that pipe \"yes\" into it.",
   "counterpoint": "\"A bypass flag invites accidents.\" The prompt stays the default for people, and the flag has to be typed on purpose. Pair it with [[svc-cli-non-interactive]].",
   "fail": {
    "language": "go",
    "code": "func main() {\n\tfmt.Printf(\"Delete %d backups? [y/N] \", len(old))\n\tvar ans string\n\tfmt.Scanln(&ans) // no preview flag, no way to skip the prompt\n\tif ans == \"y\" {\n\t\tfor _, b := range old {\n\t\t\tos.Remove(b)\n\t\t}\n\t}\n}"
   },
   "pass": {
    "language": "go",
    "code": "dry := flag.Bool(\"dry-run\", false, \"list what would be deleted, delete nothing\")\nyes := flag.Bool(\"yes\", false, \"do not ask for confirmation\")\nflag.Parse()\nfor _, b := range old {\n\tfmt.Println(b)\n}\nif *dry {\n\treturn\n}\nif !*yes && !confirm(fmt.Sprintf(\"Delete %d backups?\", len(old))) {\n\tos.Exit(1)\n}\nfor _, b := range old {\n\tif err := os.Remove(b); err != nil {\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n}"
   },
   "sources": [
    "https://kubernetes.io/docs/reference/kubectl/generated/kubectl_delete/",
    "https://git-scm.com/docs/git-clean",
    "https://www.gnu.org/software/make/manual/html_node/Options-Summary.html",
    "https://cli.github.com/manual/gh_repo_delete",
    "https://learn.microsoft.com/en-us/cli/azure/group",
    "https://cloud.google.com/sdk/docs/scripting-gcloud",
    "https://developer.hashicorp.com/terraform/cli/commands/apply",
    "https://clig.dev/",
    "https://github.com/cursor/plugins/blob/main/cli-for-agent/skills/cli-for-agents/SKILL.md"
   ],
   "orgs": [
    {
     "id": "kubernetes",
     "name": "Kubernetes",
     "sources": [
      "https://kubernetes.io/docs/reference/kubectl/generated/kubectl_delete/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "git",
     "name": "Git",
     "sources": [
      "https://git-scm.com/docs/git-clean"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gnu",
     "name": "GNU Project",
     "sources": [
      "https://www.gnu.org/software/make/manual/html_node/Options-Summary.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://cli.github.com/manual/gh_repo_delete"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/cli/azure/group"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://cloud.google.com/sdk/docs/scripting-gcloud"
     ],
     "strength": "unjudged"
    },
    {
     "id": "hashicorp",
     "name": "HashiCorp",
     "sources": [
      "https://developer.hashicorp.com/terraform/cli/commands/apply"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cursor",
     "name": "Cursor",
     "sources": [
      "https://github.com/cursor/plugins/blob/main/cli-for-agent/skills/cli-for-agents/SKILL.md"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "clig",
     "name": "Command Line Interface Guidelines",
     "sources": [
      "https://clig.dev/"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-cli#svc-cli-destructive-dry-run",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-cli-exit-codes-and-streams",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "correctness",
    "maintainability"
   ],
   "topics": [
    "cli",
    "automation"
   ],
   "lang": "core",
   "applies_when": "The code ships a command-line tool that scripts or other programs may run.",
   "context": [
    "cli"
   ],
   "tool_gate": "CLI test that runs a failing command and checks the exit code and that stdout is empty.",
   "note": "Non-zero exit on failure backed by Google (gcloud), GitHub (gh exit codes), AWS (CLI return codes), HashiCorp (terraform plan) and The Open Group (POSIX); results on stdout and diagnostics on stderr backed by The Open Group (POSIX), Heroku (CLI style guide) and the Rust CLI working group; clig.dev is a reference",
   "lacks": null,
   "rule": "Exit non-zero when a command fails, and write results to stdout and errors and progress to stderr.",
   "why": "Scripts branch on the exit code and pipe stdout into the next program. A failure that exits 0 reads as success, and an error line on stdout lands in the next program's input. Add a `--json` flag for structured results, and keep prompts out of non-terminal runs (see [[svc-cli-non-interactive]]).",
   "counterpoint": "\"Some tools use exit codes for non-error results.\" Documented codes for states such as \"differences found\" are fine. The rule is that a failure never exits 0.",
   "fail": {
    "language": "go",
    "code": "if err := run(); err != nil {\n\tfmt.Println(\"error:\", err) // error goes into the pipe\n}\n// main returns: exit status 0"
   },
   "pass": {
    "language": "go",
    "code": "if err := run(); err != nil {\n\tfmt.Fprintln(os.Stderr, \"error:\", err)\n\tos.Exit(1)\n}"
   },
   "sources": [
    "https://cloud.google.com/sdk/docs/scripting-gcloud",
    "https://cli.github.com/manual/gh_help_exit-codes",
    "https://docs.aws.amazon.com/cli/latest/userguide/cli-usage-returncodes.html",
    "https://developer.hashicorp.com/terraform/cli/commands/plan",
    "https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap01.html",
    "https://devcenter.heroku.com/articles/cli-style-guide",
    "https://rust-cli.github.io/book/tutorial/output.html",
    "https://clig.dev/"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://cloud.google.com/sdk/docs/scripting-gcloud"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://cli.github.com/manual/gh_help_exit-codes"
     ],
     "strength": "unjudged"
    },
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://docs.aws.amazon.com/cli/latest/userguide/cli-usage-returncodes.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "hashicorp",
     "name": "HashiCorp",
     "sources": [
      "https://developer.hashicorp.com/terraform/cli/commands/plan"
     ],
     "strength": "unjudged"
    },
    {
     "id": "opengroup",
     "name": "The Open Group",
     "sources": [
      "https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap01.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "heroku",
     "name": "Heroku",
     "sources": [
      "https://devcenter.heroku.com/articles/cli-style-guide"
     ],
     "strength": "unjudged"
    },
    {
     "id": "rust",
     "name": "Rust project",
     "sources": [
      "https://rust-cli.github.io/book/tutorial/output.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "clig",
     "name": "Command Line Interface Guidelines",
     "sources": [
      "https://clig.dev/"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-cli#svc-cli-exit-codes-and-streams",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-cli-non-interactive",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "maintainability",
    "process"
   ],
   "topics": [
    "cli",
    "automation"
   ],
   "lang": "core",
   "applies_when": "The code ships a command-line tool that scripts or agents may run.",
   "context": [
    "cli"
   ],
   "tool_gate": "CLI test that runs every command with stdin closed.",
   "note": "Rule backed by GitHub (GH_PROMPT_DISABLED, --title/--body skip the prompt), Google (gcloud --quiet), HashiCorp (terraform -input=false) and Microsoft (az core.disable_confirm_prompt); failing at once with an example call when a flag is missing has one org (clig.dev, a reference), so it sits in Why",
   "lacks": null,
   "rule": "Let the caller turn every prompt off with a flag or an environment variable, so the command never waits on a terminal.",
   "why": "A script or an agent has no one to answer a prompt, so the command hangs until a timeout kills it. When stdin is not a terminal, the best behaviour is to fail at once with an error that names the missing flag and shows an example call, so the caller fixes it in one step.",
   "counterpoint": "\"Prompts are friendlier for people.\" Keep them. The rule only asks that the caller can switch prompts off.",
   "fail": {
    "language": "go",
    "code": "env := flag.String(\"env\", \"\", \"target environment\")\nflag.Parse()\nif *env == \"\" {\n\tfmt.Print(\"Choose environment: \")\n\tfmt.Scanln(env) // always prompts: no way to turn it off, hangs in CI\n}"
   },
   "pass": {
    "language": "go",
    "code": "env := flag.String(\"env\", \"\", \"target environment\")\nnoInput := flag.Bool(\"no-input\", false, \"never prompt\")\nflag.Parse()\nif *env == \"\" {\n\tif *noInput || !term.IsTerminal(int(os.Stdin.Fd())) { // golang.org/x/term\n\t\tfmt.Fprintln(os.Stderr, \"error: --env is required. Example: deploy --env staging\")\n\t\tos.Exit(2)\n\t}\n\tfmt.Fprint(os.Stderr, \"Choose environment: \")\n\tfmt.Scanln(env)\n}"
   },
   "sources": [
    "https://cli.github.com/manual/gh_help_environment",
    "https://cli.github.com/manual/gh_pr_create",
    "https://cloud.google.com/sdk/docs/scripting-gcloud",
    "https://developer.hashicorp.com/terraform/cli/commands/plan",
    "https://developer.hashicorp.com/terraform/tutorials/automation/automate-terraform",
    "https://learn.microsoft.com/en-us/cli/azure/azure-cli-configuration",
    "https://clig.dev/",
    "https://github.com/cursor/plugins/blob/main/cli-for-agent/skills/cli-for-agents/SKILL.md"
   ],
   "orgs": [
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://cli.github.com/manual/gh_help_environment",
      "https://cli.github.com/manual/gh_pr_create"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://cloud.google.com/sdk/docs/scripting-gcloud"
     ],
     "strength": "unjudged"
    },
    {
     "id": "hashicorp",
     "name": "HashiCorp",
     "sources": [
      "https://developer.hashicorp.com/terraform/cli/commands/plan",
      "https://developer.hashicorp.com/terraform/tutorials/automation/automate-terraform"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/cli/azure/azure-cli-configuration"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cursor",
     "name": "Cursor",
     "sources": [
      "https://github.com/cursor/plugins/blob/main/cli-for-agent/skills/cli-for-agents/SKILL.md"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "clig",
     "name": "Command Line Interface Guidelines",
     "sources": [
      "https://clig.dev/"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-cli#svc-cli-non-interactive",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-cors-allowlist",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "security"
   ],
   "topics": [
    "cors",
    "http"
   ],
   "lang": "core",
   "applies_when": "The service sends CORS headers and browsers send credentials or cookies to it, or the API is not meant to be public. A public bearer-key API with `*` and no credentials is outside it.",
   "context": [
    "server"
   ],
   "tool_gate": "Integration test that sends a hostile `Origin` and asserts it is not echoed.",
   "note": null,
   "lacks": null,
   "rule": "Allow cross-origin access only to a fixed list of named origins, and never echo the request `Origin` header back unchecked.",
   "why": "Reflecting any Origin, or allowing `*` on an endpoint that returns private data, lets a hostile page read responses. CORS also does not stop CSRF, so it is no substitute for [[svc-csrf-state-changing]]. A subdomain regex that matches `evil-example.com` is the same bug, and so is allowing the `null` origin.",
   "counterpoint": "\"`*` is fine for a public API.\" It is, for endpoints that return only public data and take no credentials. Keep it off any endpoint behind a login.",
   "fail": {
    "language": "text",
    "code": "resp.headers[\"Access-Control-Allow-Origin\"] = request.headers[\"Origin\"]\nresp.headers[\"Access-Control-Allow-Credentials\"] = \"true\""
   },
   "pass": {
    "language": "text",
    "code": "ALLOWED = {\"https://app.example.com\"}\nif request.headers.get(\"Origin\") in ALLOWED:\n    resp.headers[\"Access-Control-Allow-Origin\"] = request.headers[\"Origin\"]\nresp.headers[\"Vary\"] = \"Origin\"                # caches keep one copy per origin"
   },
   "sources": [
    "https://cheatsheetseries.owasp.org/cheatsheets/HTML5_Security_Cheat_Sheet.html",
    "https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html",
    "https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS",
    "https://portswigger.net/web-security/cors",
    "https://codeql.github.com/codeql-query-help/javascript/js-cors-misconfiguration-for-credentials/"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/HTML5_Security_Cheat_Sheet.html",
      "https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS"
     ],
     "strength": "unjudged"
    },
    {
     "id": "portswigger",
     "name": "PortSwigger",
     "sources": [
      "https://portswigger.net/web-security/cors"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://codeql.github.com/codeql-query-help/javascript/js-cors-misconfiguration-for-credentials/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-cors-allowlist",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-csp-strict-script-src",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "security"
   ],
   "topics": [
    "http",
    "headers",
    "xss"
   ],
   "lang": "core",
   "applies_when": "The service serves HTML pages to browsers.",
   "context": [
    "server"
   ],
   "tool_gate": "Google CSP Evaluator on the policy; a CI header check against a staging URL.",
   "note": "follows OWASP, MDN and web.dev, which are stricter than the ASVS 3.4.3 minimum (that minimum still allows a host allowlist); fn-no-raw-html-sink is the first defence against XSS and CSP is the second",
   "lacks": null,
   "rule": "Send a `Content-Security-Policy` whose `script-src` uses a per-response nonce or script hashes, with `object-src 'none'` and `base-uri 'none'`.",
   "why": "When an XSS bug slips through, CSP decides whether the injected script runs. A host allowlist such as `script-src 'self' cdn.example` usually has a bypass, because some allowed host serves JSONP or an old library. A nonce or hash only lets through scripts the server marked. `'strict-dynamic'` lets those scripts load their own children. Browsers that support nonces ignore an `'unsafe-inline'` fallback placed next to one, so that fallback is safe. `'unsafe-inline'` on its own turns the policy off for inline script.",
   "counterpoint": "\"A strict CSP breaks inline handlers and third-party tags.\" It does at first, so roll it out with `Content-Security-Policy-Report-Only`, move `onclick=` attributes into script files, and switch to enforce when the reports go quiet. A policy that never leaves report-only protects nothing, so give that step a date.",
   "fail": {
    "language": "text",
    "code": "Content-Security-Policy: script-src 'self' 'unsafe-inline' https://cdn.example.com"
   },
   "pass": {
    "language": "text",
    "code": "Content-Security-Policy: script-src 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none'\n<script nonce=\"r4nd0m\" src=\"/app.js\"></script>"
   },
   "sources": [
    "https://raw.githubusercontent.com/OWASP/ASVS/master/5.0/en/0x12-V3-Web-Frontend-Security.md",
    "https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html",
    "https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP",
    "https://web.dev/articles/strict-csp"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://raw.githubusercontent.com/OWASP/ASVS/master/5.0/en/0x12-V3-Web-Frontend-Security.md",
      "https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://web.dev/articles/strict-csp"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-csp-strict-script-src",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-csrf-state-changing",
   "level": "service",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "csrf",
    "sessions"
   ],
   "lang": "core",
   "applies_when": "The service authenticates browsers with cookies and changes state on requests.",
   "context": [
    "server"
   ],
   "tool_gate": "Framework CSRF middleware enabled; test that a POST without the token returns 403.",
   "note": null,
   "lacks": null,
   "rule": "Require a CSRF token (or an equivalent header check) on every cookie-authenticated request that changes state, and never change state on GET.",
   "why": "The browser attaches cookies to a cross-site request, so a hostile page can post as the user. `SameSite` helps but `Lax` still sends cookies on cross-site top-level GET, so it supports the token and does not replace it. Naive double-submit cookies fail when a sibling subdomain can set cookies.",
   "counterpoint": "\"Our API uses JSON, so browsers cannot forge it.\" A simple content type such as `text/plain` can carry a JSON body without a preflight. Reject it, or require a custom header such as `X-CSRF-Token`. Pure bearer-token APIs with no cookies are exempt.",
   "fail": {
    "language": "text",
    "code": "@app.get(\"/account/delete\")                    # state change on GET\n@app.post(\"/transfer\")                         # cookie auth, no token check"
   },
   "pass": {
    "language": "text",
    "code": "@app.post(\"/transfer\")\n@csrf_protect                                  # token bound to the session\nSet-Cookie: sid=...; SameSite=Lax; Secure; HttpOnly"
   },
   "sources": [
    "https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html",
    "https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/CSRF",
    "https://docs.djangoproject.com/en/5.2/howto/csrf/",
    "https://www.rfc-editor.org/rfc/rfc9110.html"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/CSRF"
     ],
     "strength": "unjudged"
    },
    {
     "id": "django",
     "name": "Django Software Foundation",
     "sources": [
      "https://docs.djangoproject.com/en/5.2/howto/csrf/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "ietf",
     "name": "IETF",
     "sources": [
      "https://www.rfc-editor.org/rfc/rfc9110.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-csrf-state-changing",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-decompression-cap",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "security",
    "performance"
   ],
   "topics": [
    "dos"
   ],
   "lang": "core",
   "applies_when": "The code decompresses or decodes input whose size it does not control.",
   "context": [
    "server"
   ],
   "tool_gate": "Apache `DeflateInflateLimitRequestBody`; ASP.NET Core `MaxRequestBodySize`; a test that inflates a 1 MB gzip to about 1 GB and asserts the decoder stops at the cap.",
   "note": null,
   "lacks": null,
   "rule": "Cap the decompressed output of every decoder, and set the cap on the output rather than the compressed input.",
   "why": "A small gzip can expand a thousand-fold: DEFLATE tops out near 1030:1, so a 1 MB gzip can inflate to about 1 GB, and nested archives or other codecs can go further. A body limit on the compressed bytes does not bound that expansion, so the decoder needs its own ceiling. `Content-Length` counts the compressed stream, not the inflated body, and Apache's inflated-body directive is unset by default. In Node, the convenience methods take `maxOutputLength`, whose default is `buffer.kMaxLength`, so pass a real number. The zip entry iterator is a separate case: `zipEntry.contentIterator()` has no default `maxSize` and is not covered by the zip-wide ceiling, so pass `maxSize` on that call. Python's tar filters address paths and links, and its docs say they do not prevent every unsafe archive, so size still needs its own bound. See also [[svc-bound-client-controlled-sizes]] for the request-body cap and [[svc-upload-validate-store]] for archives in uploads.",
   "counterpoint": "\"The proxy already caps the request body.\" It caps the compressed bytes, which is the wrong number. A 1 MB gzip can still inflate to about 1 GB, and `Content-Length` says nothing about the decompressed size. Set the inflated cap at the decoder and keep the body cap. Where the platform offers a decompression limit, check that it is on: Apache's directive falls back to the post-deflation `LimitRequestBody`, and a value of `0` removes the inflated-body limit.",
   "fail": {
    "language": "javascript",
    "code": "const body = zlib.gunzipSync(req.body); // no maxOutputLength\nparse(body);"
   },
   "pass": {
    "language": "javascript",
    "code": "const body = zlib.gunzipSync(req.body, { maxOutputLength: 1 << 20 });\nparse(body);"
   },
   "sources": [
    "https://nodejs.org/api/zlib.html",
    "https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/request-decompression?view=aspnetcore-10.0",
    "https://httpd.apache.org/docs/2.4/mod/mod_deflate.html",
    "https://docs.python.org/3/library/tarfile.html"
   ],
   "orgs": [
    {
     "id": "nodejs",
     "name": "OpenJS Foundation",
     "sources": [
      "https://nodejs.org/api/zlib.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/request-decompression?view=aspnetcore-10.0"
     ],
     "strength": "unjudged"
    },
    {
     "id": "apache",
     "name": "Apache Software Foundation",
     "sources": [
      "https://httpd.apache.org/docs/2.4/mod/mod_deflate.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://docs.python.org/3/library/tarfile.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-decompression-cap",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-dlq",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "reliability"
   ],
   "topics": [
    "queues",
    "resilience"
   ],
   "lang": "core",
   "applies_when": "The code consumes from a queue or stream.",
   "context": [
    "server"
   ],
   "tool_gate": "A config check that every consumed queue has a dead-letter target (for SQS in Terraform, a `redrive_policy` with `maxReceiveCount`), and an alarm on dead-letter queue depth.",
   "note": "dead-letter queue and replay backed by AWS (alarm on any message, re-process after the fix), Netflix (failed events spill into dead-letter queues, an offline job replays them), Notion (re-enqueue from the dead-letter queue is a first-class workflow) and Uber (dead-letter messages are published back into the first retry topic); the retry cap (AWS) and the depth alarm (AWS) sit in Why; Microsoft, Google and AWS back the ordered-queue clause",
   "lacks": null,
   "rule": "Give every consumer queue a dead-letter queue and keep a way to replay it after the fix. On an ordered queue, key the group or session to the unit that must stay ordered, so a poison message holds only its own unit.",
   "why": "A poison message otherwise loops forever, uses the consumers' capacity and hides the real backlog. On an ordered queue it also holds the messages behind it: in a FIFO queue no further messages from the same group are returned until the stuck message is deleted or becomes visible again, and an unacknowledged ordering key can delay the others. Give each independent stream its own group, session or queue, so one failure stays contained. Then cap the retries, so that a failing message moves to the dead-letter queue after a few attempts. Alarm on any message in the dead-letter queue, because each one is a bug to fix. Replay has a cost on these queues: a message put back after a dead-letter loses its place in the order, so replay only where order no longer matters, or reorder downstream.",
   "counterpoint": "\"A dead-letter queue already stops the poison message.\" It stops it after the retry cap. Until then the redelivery holds its group, and in a FIFO queue that means every message behind it. Grouping and the dead-letter queue do different jobs, so keep both. Choose the group key from the unit that must stay ordered. A single group restores the head-of-line block you were trying to remove. On a standard queue with no ordering, there is no group to key and the second sentence of the Rule does not apply.",
   "fail": {
    "language": "hcl",
    "code": "resource \"aws_sqs_queue\" \"orders\" {\n  name       = \"orders.fifo\"\n  fifo_queue = true\n  # no redrive_policy: a message that always fails is retried forever\n  # and holds its whole message group\n}"
   },
   "pass": {
    "language": "hcl",
    "code": "resource \"aws_sqs_queue\" \"orders_dlq\" {\n  name       = \"orders-dlq.fifo\"\n  fifo_queue = true\n}\n\nresource \"aws_sqs_queue\" \"orders\" {\n  name       = \"orders.fifo\"\n  fifo_queue = true\n  redrive_policy = jsonencode({\n    deadLetterTargetArn = aws_sqs_queue.orders_dlq.arn\n    maxReceiveCount     = 5\n  })\n}\n# replay after the fix: aws sqs start-message-move-task --source-arn <orders-dlq ARN>"
   },
   "sources": [
    "https://aws.amazon.com/builders-library/avoiding-insurmountable-queue-backlogs/",
    "https://netflixtechblog.com/behind-the-scenes-evolving-netflixs-ads-event-pipeline-for-live-part-ii-826ebf9ad9fb",
    "https://www.notion.com/blog/building-observability-into-notions-dead-letter-queue",
    "https://www.uber.com/blog/reliable-reprocessing/",
    "https://learn.microsoft.com/en-us/azure/service-bus-messaging/message-sessions",
    "https://cloud.google.com/pubsub/docs/ordering",
    "https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues-understanding-logic.html"
   ],
   "orgs": [
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://aws.amazon.com/builders-library/avoiding-insurmountable-queue-backlogs/",
      "https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues-understanding-logic.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "netflix",
     "name": "Netflix",
     "sources": [
      "https://netflixtechblog.com/behind-the-scenes-evolving-netflixs-ads-event-pipeline-for-live-part-ii-826ebf9ad9fb"
     ],
     "strength": "unjudged"
    },
    {
     "id": "notion",
     "name": "Notion",
     "sources": [
      "https://www.notion.com/blog/building-observability-into-notions-dead-letter-queue"
     ],
     "strength": "supports"
    },
    {
     "id": "uber",
     "name": "Uber",
     "sources": [
      "https://www.uber.com/blog/reliable-reprocessing/"
     ],
     "strength": "supports"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/service-bus-messaging/message-sessions"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://cloud.google.com/pubsub/docs/ordering"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/service-server#svc-dlq",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-export-scope",
   "level": "service",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "data",
    "multi-tenant"
   ],
   "lang": "core",
   "applies_when": "An endpoint or job produces a file, archive or stream of many records.",
   "context": [
    "data"
   ],
   "tool_gate": "A test that exports as a second tenant and asserts the other tenant's rows are absent.",
   "note": null,
   "lacks": null,
   "rule": "Scope an export to what the requesting principal may read.",
   "why": "An export is a read that returns many rows at once. The per-object check on the detail page never runs, so a `SELECT *` with no tenant or access predicate hands the caller the table. Apply the same filter the list endpoint uses, including any role or ownership predicate and not only the tenant, and apply it inside the export query rather than on rows already fetched. OWASP's examples put the tenant scope in the query itself. Test it with two tenants. A restore asks the same question in reverse: a shared backup holds several tenants and old memberships, so restore one tenant into a separate resource and re-apply current access and retention before anyone can read it. A recovered environment also needs the production controls, not the ones it had when the backup was taken.",
   "counterpoint": "\"Some exports deliberately include everything.\" Some products ship an admin XML export that includes pages the exporter cannot view. That path is an administrative backup, so gate it on an admin permission and audit it. The user-facing exports in the same products contain only what the exporter can view, which is the rule here. Check which export path the change touches before calling a missing filter a bug.",
   "fail": {
    "language": "text",
    "code": "GET /export  ->  SELECT * FROM orders; streamed to any authenticated caller"
   },
   "pass": {
    "language": "python",
    "code": "def export(user):\n    rows = session.query(Order).filter(Order.tenant_id == user.tenant_id)\n    return csv_stream(rows)"
   },
   "sources": [
    "https://cheatsheetseries.owasp.org/cheatsheets/Multi_Tenant_Security_Cheat_Sheet.html",
    "https://confluence.atlassian.com/display/CONF719/Export+Content+to+Word,+PDF,+HTML+and+XML",
    "https://learn.microsoft.com/en-us/power-bi/visuals/power-bi-visualization-export-data",
    "https://learn.microsoft.com/en-us/azure/architecture/guide/multitenant/approaches/storage-data",
    "https://cloud.google.com/architecture/dr-scenarios-planning-guide"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Multi_Tenant_Security_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "atlassian",
     "name": "Atlassian",
     "sources": [
      "https://confluence.atlassian.com/display/CONF719/Export+Content+to+Word,+PDF,+HTML+and+XML"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/power-bi/visuals/power-bi-visualization-export-data",
      "https://learn.microsoft.com/en-us/azure/architecture/guide/multitenant/approaches/storage-data"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://cloud.google.com/architecture/dr-scenarios-planning-guide"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-data#svc-export-scope",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-fail-closed-defaults",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "security"
   ],
   "topics": [
    "configuration"
   ],
   "lang": "core",
   "applies_when": "The service reads configuration for authentication, an allow-list, a safety check, or a signing secret.",
   "context": [
    "server"
   ],
   "tool_gate": "A startup check that raises on a missing or placeholder secret (Django refuses an unset `SECRET_KEY`; Pydantic Settings fields without a default); a test that boots the service with an empty environment and expects a failed start or denied requests.",
   "note": "A route that forgets a login check is a separate defect; this card covers a control that is switched off, empty, or backed by a placeholder until someone configures it. sys-secrets-and-errors covers errors that continue after a failed check",
   "lacks": null,
   "rule": "Ship every security control switched on, with an empty allow-list denying everything.",
   "why": "A control that is off until configured is off in the deployment where nobody read the setup page. An empty allow-list that means \"allow all\" and a built-in `changeme` key both boot cleanly, so nothing alerts anyone while the service runs open or signs tokens anyone can forge. Refuse to start when a secret is missing or still a placeholder: failing at startup moves the discovery from an incident to the first deploy.",
   "counterpoint": "\"Developers need to run it locally without setup.\" Give them a separate dev entry point or a dev-only flag that is named for what it does (`INSECURE_DEV_MODE=1`) and that production images never set. The default stays closed, and the open mode is a visible, greppable choice.",
   "fail": {
    "language": "python",
    "code": "import os\n\nAUTH_ENABLED = os.environ.get(\"AUTH_ENABLED\", \"false\") == \"true\"\nALLOWED_HOSTS = os.environ.get(\"ALLOWED_HOSTS\", \"\").split(\",\")  # \"\" means no filtering below\nSECRET_KEY = os.environ.get(\"SECRET_KEY\", \"changeme\")\n\ndef host_ok(host):\n    return not ALLOWED_HOSTS[0] or host in ALLOWED_HOSTS"
   },
   "pass": {
    "language": "python",
    "code": "import os\n\nAUTH_ENABLED = os.environ.get(\"AUTH_ENABLED\", \"true\") == \"true\"\nALLOWED_HOSTS = [h for h in os.environ.get(\"ALLOWED_HOSTS\", \"\").split(\",\") if h]\nSECRET_KEY = os.environ[\"SECRET_KEY\"]          # KeyError stops the boot\nif SECRET_KEY in {\"\", \"changeme\"}:\n    raise RuntimeError(\"SECRET_KEY is a placeholder\")\n\ndef host_ok(host):\n    return host in ALLOWED_HOSTS               # empty list denies all"
   },
   "sources": [
    "https://raw.githubusercontent.com/OWASP/ASVS/master/5.0/en/0x22-V13-Configuration.md",
    "https://learn.microsoft.com/en-us/aspnet/core/security/authorization/secure-data",
    "https://google.github.io/building-secure-and-reliable-systems/raw/ch08.html",
    "https://docs.djangoproject.com/en/5.2/ref/settings/",
    "https://cwe.mitre.org/data/definitions/1393.html",
    "https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_evaluation-logic_policy-eval-denyallow.html",
    "https://www.cisa.gov/sites/default/files/2023-10/SecureByDesign_1025_508c.pdf",
    "https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html",
    "https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_catalog.json"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://raw.githubusercontent.com/OWASP/ASVS/master/5.0/en/0x22-V13-Configuration.md",
      "https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/aspnet/core/security/authorization/secure-data"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://google.github.io/building-secure-and-reliable-systems/raw/ch08.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "django",
     "name": "Django Software Foundation",
     "sources": [
      "https://docs.djangoproject.com/en/5.2/ref/settings/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mitre",
     "name": "MITRE",
     "sources": [
      "https://cwe.mitre.org/data/definitions/1393.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_evaluation-logic_policy-eval-denyallow.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cisa",
     "name": "CISA",
     "sources": [
      "https://www.cisa.gov/sites/default/files/2023-10/SecureByDesign_1025_508c.pdf"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nist",
     "name": "NIST",
     "sources": [
      "https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_catalog.json"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-fail-closed-defaults",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-host-header-trust",
   "level": "service",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "http",
    "authn"
   ],
   "lang": "core",
   "applies_when": "The service builds an absolute URL from the request, or reads `Host` or `X-Forwarded-*` to decide what to serve or where to send a user.",
   "context": [
    "server"
   ],
   "tool_gate": "Framework host allowlist configured (`ALLOWED_HOSTS`, `AllowedHosts`, `config.hosts`, `trustHosts`), and a test that sends a hostile `Host` and expects 400.",
   "note": "fn-redirect-target-allowlist checks where a redirect points; this card checks how a URL is built and which Host values the service answers. configured base URL - PortSwigger, Fastly and Rails; allowlist - PortSwigger, Fastly, Django, Microsoft, Rails and Laravel; trusted proxy - Django, Microsoft, PortSwigger and Fastly; duplicate Host has IETF and Fastly only, so it is in Why",
   "lacks": null,
   "rule": "Build absolute URLs from configured values, answer only for a `Host` on an allowlist, and take `X-Forwarded-*` values only from a trusted proxy.",
   "why": "A reset link built from the request `Host` carries whatever domain the attacker sent. The victim asks for a reset, the mail arrives with the attacker's host, and the token goes to the attacker when the link is opened. The same value drives routing, cache keys, and absolute redirects. A forwarded header is worse, because many stacks accept `X-Forwarded-Host` from any client and use it in place of `Host`. Two `Host` headers are a second route to the same disagreement: one component validates the first and another reads the last, so reject a request that carries more than one.",
   "counterpoint": "\"A reverse proxy sets `Host`, so the value can be trusted.\" It can only be trusted when the proxy overwrites any client-supplied value and the app accepts requests only from the proxy's address. An app that is reachable directly, or a proxy that passes the client header through, hands the value back to the attacker.",
   "fail": {
    "language": "python",
    "code": "reset = f\"https://{request.headers['Host']}/reset?token={token}\"\n# no allowlist, and X-Forwarded-Host is honoured from any client"
   },
   "pass": {
    "language": "python",
    "code": "reset = f\"{settings.PUBLIC_BASE_URL}/reset?token={token}\"\nALLOWED_HOSTS = [\"app.example.com\"]      # Django; any other Host: 400\nTRUSTED_PROXIES = {\"10.0.0.10\"}          # pseudo-config: your proxy middleware's setting\n                                         # (Django: USE_X_FORWARDED_HOST stays off unless a proxy sets it)"
   },
   "sources": [
    "https://portswigger.net/web-security/host-header",
    "https://www.fastly.com/learning/security/what-are-http-host-header-attacks",
    "https://docs.djangoproject.com/en/5.2/ref/settings/",
    "https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel/host-filtering",
    "https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer",
    "https://guides.rubyonrails.org/action_mailer_basics.html",
    "https://guides.rubyonrails.org/configuring.html",
    "https://laravel.com/docs/12.x/passwords",
    "https://www.rfc-editor.org/rfc/rfc9112.html"
   ],
   "orgs": [
    {
     "id": "portswigger",
     "name": "PortSwigger",
     "sources": [
      "https://portswigger.net/web-security/host-header"
     ],
     "strength": "unjudged"
    },
    {
     "id": "fastly",
     "name": "Fastly",
     "sources": [
      "https://www.fastly.com/learning/security/what-are-http-host-header-attacks"
     ],
     "strength": "unjudged"
    },
    {
     "id": "django",
     "name": "Django Software Foundation",
     "sources": [
      "https://docs.djangoproject.com/en/5.2/ref/settings/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel/host-filtering",
      "https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer"
     ],
     "strength": "unjudged"
    },
    {
     "id": "rails",
     "name": "Ruby on Rails",
     "sources": [
      "https://guides.rubyonrails.org/action_mailer_basics.html",
      "https://guides.rubyonrails.org/configuring.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "laravel",
     "name": "Laravel",
     "sources": [
      "https://laravel.com/docs/12.x/passwords"
     ],
     "strength": "unjudged"
    },
    {
     "id": "ietf",
     "name": "IETF",
     "sources": [
      "https://www.rfc-editor.org/rfc/rfc9112.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-host-header-trust",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-idempotency-key",
   "level": "service",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "reliability",
    "correctness"
   ],
   "topics": [
    "resilience"
   ],
   "lang": "core",
   "applies_when": "A non-idempotent operation (charge, create, send) can be retried by a client, a queue, or a saga.",
   "context": [
    "server"
   ],
   "tool_gate": null,
   "note": null,
   "lacks": null,
   "rule": "Make every retryable write idempotent: the caller sends a unique key, and a retry with the same key never applies the write twice.",
   "why": "A timeout does not say whether the first attempt ran. Retrying without a key charges twice or sends two emails. A common way to answer the retry is to store the first result and return it again.",
   "counterpoint": "\"Our retries are rare.\" Queues redeliver and sagas retry by design, so rare is not never. The cost is a unique key and a stored result. No standard fixes how long the server keeps a key. Pick a window and publish it.",
   "fail": {
    "language": "text",
    "code": "POST /payments {amount: 10}          # retried on timeout\nINSERT INTO payment(amount) VALUES (10)"
   },
   "pass": {
    "language": "text",
    "code": "POST /payments  Idempotency-Key: k-123\nINSERT ... ON CONFLICT (idem_key) DO UPDATE SET idem_key = EXCLUDED.idem_key RETURNING *"
   },
   "sources": [
    "https://learn.microsoft.com/en-us/azure/architecture/patterns/retry",
    "https://learn.microsoft.com/en-us/azure/architecture/patterns/saga",
    "https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/",
    "https://docs.stripe.com/api/idempotent_requests"
   ],
   "orgs": [
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/architecture/patterns/retry",
      "https://learn.microsoft.com/en-us/azure/architecture/patterns/saga"
     ],
     "strength": "unjudged"
    },
    {
     "id": "ietf",
     "name": "IETF",
     "sources": [
      "https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "stripe",
     "name": "Stripe",
     "sources": [
      "https://docs.stripe.com/api/idempotent_requests"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-idempotency-key",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-invalid-input-is-4xx",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "correctness",
    "reliability"
   ],
   "topics": [
    "api",
    "validation"
   ],
   "lang": "core",
   "applies_when": "A handler takes a number, an id, a header or a body field from the caller and uses it in a query, a calculation or an encoder.",
   "context": [
    "server"
   ],
   "tool_gate": "Schema or model constraints on every field (minimum, maximum, enum, format); a test that posts a negative number, an unknown id and a non-ASCII header and expects a 4xx.",
   "note": "The domain check and the reject are backed by OWASP, Google (gRPC INVALID_ARGUMENT, user code only) and Kubernetes (422). The 4xx class is backed by IETF, Microsoft, Stripe, Google and Kubernetes. A missing id answering 404 is backed by IETF, Google (NOT_FOUND) and Kubernetes. Stripe and Microsoft state the status for a bad request, not for a foreign key. Which 4xx and where the check lives are covered by other cards",
   "lacks": null,
   "rule": "Check each caller-supplied value against its allowed domain (range, allowed set, an id that must exist, an encoding the code can handle) at the boundary, and answer a failed check with a 4xx, never a 5xx.",
   "why": "A negative quota, a limit of zero, an id with no row, or a header with a non-ASCII character is the caller's error. If it reaches the database or an `.encode(\"ascii\")`, the user gets a 500: the caller retries, the error-rate alert fires, and a negative quota may be stored and later allow unlimited use. A schema constraint or an explicit lookup costs one line per field. [[svc-bound-client-controlled-sizes]] caps size and count; this card covers every other value.",
   "counterpoint": "\"The database has a CHECK and a foreign key, so the data is safe.\" It is, but the constraint violation surfaces as a 500 unless the code maps it. Keep the constraint as the last line and validate first, so the caller gets a message that names the field. Catching the database error and mapping it to a 4xx also meets this card when a pre-check would race.",
   "fail": {
    "language": "python",
    "code": "@app.post(\"/quotas\")\ndef set_quota(body: dict):\n    db.execute(\"UPDATE plans SET quota=%s WHERE id=%s\", (body[\"quota\"], body[\"plan_id\"]))\n    # quota=-5 is stored; a plan_id with no row updates nothing and answers 200\n    return {\"ok\": True}\n\ndef sign(request):\n    return hmac_header(request.headers[\"X-Name\"].encode(\"ascii\"))   # \"Zoë\" -> UnicodeEncodeError -> 500"
   },
   "pass": {
    "language": "python",
    "code": "class SetQuota(BaseModel):\n    quota: int = Field(ge=0, le=10_000)\n    plan_id: int\n\n@app.post(\"/quotas\")\ndef set_quota(body: SetQuota):\n    if not db.exists(\"SELECT 1 FROM plans WHERE id=%s\", (body.plan_id,)):\n        raise HTTPException(404, \"unknown plan_id\")\n    db.execute(\"UPDATE plans SET quota=%s WHERE id=%s\", (body.quota, body.plan_id))\n\ndef sign(request):\n    try:\n        name = request.headers[\"X-Name\"].encode(\"ascii\")\n    except UnicodeEncodeError:\n        raise HTTPException(400, \"X-Name must be ASCII\")\n    return hmac_header(name)"
   },
   "sources": [
    "https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html",
    "https://grpc.io/docs/guides/status-codes/",
    "https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md",
    "https://www.rfc-editor.org/rfc/rfc9110.html#name-client-error-4xx",
    "https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design",
    "https://docs.stripe.com/api/errors"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://grpc.io/docs/guides/status-codes/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "kubernetes",
     "name": "Kubernetes",
     "sources": [
      "https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "ietf",
     "name": "IETF",
     "sources": [
      "https://www.rfc-editor.org/rfc/rfc9110.html#name-client-error-4xx"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design"
     ],
     "strength": "unjudged"
    },
    {
     "id": "stripe",
     "name": "Stripe",
     "sources": [
      "https://docs.stripe.com/api/errors"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-invalid-input-is-4xx",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-list-endpoint-paginates",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "performance"
   ],
   "topics": [
    "api"
   ],
   "lang": "core",
   "applies_when": "The service exposes an endpoint or RPC that returns a collection.",
   "context": [
    "server"
   ],
   "tool_gate": "Contract test that calls the list with a huge page size and asserts the clamp or a 400.",
   "note": "5 orgs back pagination and 4 state a server-side maximum (Zalando's maximum was not seen); a clamp or a 400 are both accepted",
   "lacks": null,
   "rule": "Paginate every list endpoint, with a default page size and a server-side maximum.",
   "why": "A list that returns everything grows with the data until one request exhausts memory or the timeout. Adding pagination later breaks clients, so start with it. Without a maximum, a caller can ask for a page as big as the table.",
   "counterpoint": "\"This list is always small.\" Say so with a bounded type or a documented limit, since data tends to outgrow its first estimate. Cursors cost more than offsets to build, but they stay stable while rows change.",
   "fail": {
    "language": "text",
    "code": "GET /orders          -> every order the user ever placed\nGET /orders?limit=1000000   (server honours it)"
   },
   "pass": {
    "language": "text",
    "code": "GET /orders?page_size=50&page_token=abc   (default 25, values above 100 clamped to 100)\nresponse: { \"items\": [...], \"next_page_token\": \"def\" }"
   },
   "sources": [
    "https://google.aip.dev/158",
    "https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design",
    "https://opensource.zalando.com/restful-api-guidelines/",
    "https://docs.github.com/en/rest/using-the-rest-api/using-pagination-in-the-rest-api",
    "https://docs.stripe.com/api/pagination"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://google.aip.dev/158"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design"
     ],
     "strength": "unjudged"
    },
    {
     "id": "zalando",
     "name": "Zalando",
     "sources": [
      "https://opensource.zalando.com/restful-api-guidelines/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://docs.github.com/en/rest/using-the-rest-api/using-pagination-in-the-rest-api"
     ],
     "strength": "unjudged"
    },
    {
     "id": "stripe",
     "name": "Stripe",
     "sources": [
      "https://docs.stripe.com/api/pagination"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-list-endpoint-paginates",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-mfa-step-up",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "security"
   ],
   "topics": [
    "authn",
    "mfa"
   ],
   "lang": "core",
   "applies_when": "The service offers multi-factor authentication, or lets a signed-in user change a password, email address, or authentication factor.",
   "context": [
    "server"
   ],
   "tool_gate": null,
   "note": "the practical gate is an integration test: a session that passed password-only login gets a step-up challenge on every sensitive route, including factor change; svc-session-lifecycle covers timeouts, not the assurance behind the current session",
   "lacks": null,
   "rule": "Require a fresh authentication with an already-bound factor before sensitive actions and before adding, replacing or removing an MFA factor; an existing session alone is not enough.",
   "why": "A session that passed a password check says nothing about who holds it now. Let that session enrol a new phone or replace an authenticator and an attacker with a stolen cookie keeps the account after the password is changed. The same gap covers password, email and admin-role changes. Step-up at the action asks for the factor only when it matters, and a short freshness window keeps the prompt rare. A session must not be treated as stronger than the authentication that created it. A user who lost the only factor needs a documented recovery path, such as single-use recovery codes or a temporary access pass. That path is part of the control, and it is judged as a separate recovery flow.",
   "counterpoint": "\"Prompting on every sensitive action annoys users, and a user who lost the only factor gets locked out.\" Use risk and a recent-auth window so the prompt appears only when the session is stale or the action is high risk, and keep the recovery path described above; without it, step-up becomes a denial of service against the account's owner.",
   "fail": {
    "language": "text",
    "code": "@login_required\ndef add_phone():\n    user.phone = request.form[\"phone\"]      # any old session can replace the second factor"
   },
   "pass": {
    "language": "text",
    "code": "@login_required\n@recent_auth(max_age=300, factor=\"existing\")\ndef add_phone():\n    user.phone = request.form[\"phone\"]\n    notify(user, \"phone changed\")"
   },
   "sources": [
    "https://cheatsheetseries.owasp.org/cheatsheets/Multifactor_Authentication_Cheat_Sheet.html",
    "https://pages.nist.gov/800-63-4/sp800-63b/events/",
    "https://pages.nist.gov/800-63-4/sp800-63b/session/",
    "https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/protected-actions-overview",
    "https://auth0.com/docs/secure/multi-factor-authentication/step-up-authentication",
    "https://learn.microsoft.com/en-us/entra/identity/authentication/howto-registration-mfa-sspr-combined"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Multifactor_Authentication_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nist",
     "name": "NIST",
     "sources": [
      "https://pages.nist.gov/800-63-4/sp800-63b/events/",
      "https://pages.nist.gov/800-63-4/sp800-63b/session/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/protected-actions-overview",
      "https://learn.microsoft.com/en-us/entra/identity/authentication/howto-registration-mfa-sspr-combined"
     ],
     "strength": "unjudged"
    },
    {
     "id": "auth0",
     "name": "Okta (Auth0)",
     "sources": [
      "https://auth0.com/docs/secure/multi-factor-authentication/step-up-authentication"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-mfa-step-up",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-no-redundant-entry",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "usability",
    "accessibility"
   ],
   "topics": [
    "frontend",
    "forms"
   ],
   "lang": "core",
   "applies_when": "A multi-step flow, such as checkout or signup, asks for data in more than one step.",
   "context": [
    "ui"
   ],
   "tool_gate": "none; a walk through the flow that lists each field asked twice.",
   "note": "3 orgs (W3C, GOV.UK, Baymard); Baymard sells research, so it is closer to a vendor. NN/g and Shneiderman back the Why (recognition over recall), not the practice",
   "lacks": null,
   "rule": "Do not ask for information the user already gave in the same flow. Fill it in, or let the user pick it.",
   "why": "Typing the same address twice costs time and adds typos. People recognise what they gave more easily than they recall it, so show it and let them confirm it.",
   "counterpoint": "\"Retyping a new password confirms it.\" That is a fair exception. So is data that is no longer valid, and re-entry that is the point of the task, such as a memory test. Browser autofill does not count: the page itself must supply the value.",
   "fail": {
    "language": "html",
    "code": "<!-- step 1 -->  <input name=\"ship_address\">\n<!-- step 2 -->  <input name=\"billing_address\">   <!-- empty again -->"
   },
   "pass": {
    "language": "html",
    "code": "<!-- step 2 -->\n<label><input type=\"checkbox\" name=\"same\" checked> Billing address is the same as shipping</label>\n<input name=\"billing_address\" value=\"12 Elm Road, Leeds\" hidden>   <!-- hidden while the box is checked; shown, pre-filled, when it is unchecked -->"
   },
   "sources": [
    "https://www.w3.org/WAI/WCAG22/Understanding/redundant-entry.html",
    "https://www.nngroup.com/articles/ten-usability-heuristics/",
    "https://www.cs.umd.edu/users/ben/goldenrules.html",
    "https://design-system.service.gov.uk/patterns/question-pages",
    "https://baymard.com/blog/set-billing-equal-to-shipping"
   ],
   "orgs": [
    {
     "id": "w3c",
     "name": "W3C",
     "sources": [
      "https://www.w3.org/WAI/WCAG22/Understanding/redundant-entry.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nngroup",
     "name": "Nielsen Norman Group",
     "sources": [
      "https://www.nngroup.com/articles/ten-usability-heuristics/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "govuk",
     "name": "GOV.UK",
     "sources": [
      "https://design-system.service.gov.uk/patterns/question-pages"
     ],
     "strength": "unjudged"
    },
    {
     "id": "baymard",
     "name": "Baymard Institute",
     "sources": [
      "https://baymard.com/blog/set-billing-equal-to-shipping"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "shneiderman",
     "name": "Ben Shneiderman",
     "sources": [
      "https://www.cs.umd.edu/users/ben/goldenrules.html"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-ui#svc-no-redundant-entry",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-no-secrets-in-url",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "security"
   ],
   "topics": [
    "secrets",
    "http"
   ],
   "lang": "core",
   "applies_when": "A client or server puts an API key, session token, bearer token, password, or personal data into a URL.",
   "context": [
    "server"
   ],
   "tool_gate": null,
   "note": "ASVS 14.2.1; a log check that rejects query keys such as `token`, `key` or `password` in access logs is the nearest gate; sys-pii-minimise-retention and code-secret-scan cover other leak paths",
   "lacks": null,
   "rule": "Send tokens, keys, passwords, and personal data in a header or the request body, never in the path or query string.",
   "why": "A URL is copied to places nobody reviews: proxy and access logs, browser history, the `Referer` header sent to third-party scripts and images, and links pasted into chat. A token there stays readable long after the request ends. The `Authorization` header and a POST body skip all of those copies.",
   "counterpoint": "\"Email links have to carry the token in the URL.\" Make that token single-use and short-lived, have the landing page swap it for a session at once, and send `Referrer-Policy: no-referrer` on that page. A long-lived API key has no such excuse.",
   "fail": {
    "language": "text",
    "code": "GET /api/orders?api_key=sk_live_abc123\nGET /reset?email=ana@example.com&token=9f2c"
   },
   "pass": {
    "language": "text",
    "code": "GET /api/orders\nAuthorization: Bearer sk_live_abc123\n\nPOST /reset\nContent-Type: application/json\n\n{\"token\": \"9f2c\"}"
   },
   "sources": [
    "https://raw.githubusercontent.com/OWASP/ASVS/master/5.0/en/0x23-V14-Data-Protection.md",
    "https://www.rfc-editor.org/rfc/rfc6750.html",
    "https://cwe.mitre.org/data/definitions/598.html",
    "https://developer.mozilla.org/en-US/docs/Web/Security/Referer_header:_privacy_and_security_concerns"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://raw.githubusercontent.com/OWASP/ASVS/master/5.0/en/0x23-V14-Data-Protection.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "ietf",
     "name": "IETF",
     "sources": [
      "https://www.rfc-editor.org/rfc/rfc6750.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mitre",
     "name": "MITRE",
     "sources": [
      "https://cwe.mitre.org/data/definitions/598.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://developer.mozilla.org/en-US/docs/Web/Security/Referer_header:_privacy_and_security_concerns"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-no-secrets-in-url",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-oauth-code-flow-binding",
   "level": "service",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "authn",
    "oauth"
   ],
   "lang": "core",
   "applies_when": "The service acts as an OAuth 2.0 or OpenID Connect client and exchanges an authorization code for tokens.",
   "context": [
    "server"
   ],
   "tool_gate": null,
   "note": "RFC 9700 requires the authorization server to match redirect URIs exactly, which is why the client must send the registered value (RFC 6749 section 4.1.3); the loopback port exception is only for a native app's http://127.0.0.1 redirect; fn-redirect-target-allowlist covers ?next= redirects, not the registered redirect_uri, and svc-session-lifecycle covers the session issued after login",
   "lacks": null,
   "rule": "Finish the authorization-code flow with PKCE and a challenge method that keeps the verifier out of the authorization request (`S256`), send the exact registered `redirect_uri` (a native app on the loopback interface may vary the port), and reject the response unless this transaction's `state` or OIDC `nonce` matches and the ID token's signature, `iss`, `aud` and `exp` check out.",
   "why": "A code that is not bound to the browser in front of you can be injected into another session, and an ID token minted for a different client or issuer can be replayed at your login. PKCE makes an intercepted code useless without the verifier, and `S256` is the method that does not reveal the verifier in the request. An exact redirect URI stops the code reaching an attacker's host. `state` or `nonce` ties the response to the login the user started. The ID-token checks reject a token that another app, another issuer or an expired clock made.",
   "counterpoint": "\"The OAuth client library already handles this.\" It handles the protocol, not your configuration. Some libraries decode the ID token unless you pass the issuer, audience and nonce, and the state or nonce store belongs to your session code. Test with a token minted for another client and a response with no state. The loopback exception is narrow: the port may differ, the host and path may not. The card is stricter than RFC 9700, which recommends PKCE for confidential clients and lets a confidential OIDC client use `nonce` instead; a reviewer may accept that alternative when the authorization server is known to bind the code another way.",
   "fail": {
    "language": "text",
    "code": "code = request.args[\"code\"]\ntoken = exchange(code, redirect_uri=request.args[\"redirect_uri\"])\nsession[\"user\"] = jwt.decode(token[\"id_token\"], options={\"verify_signature\": False})[\"sub\"]\n# no PKCE, no state check, no ID-token validation, and the redirect_uri is taken from the request"
   },
   "pass": {
    "language": "python",
    "code": "# the authorization request carried code_challenge=...&code_challenge_method=S256\nif not secrets.compare_digest(request.args[\"state\"], session.pop(\"oauth_state\")):\n    raise PermissionDenied\ntoken = exchange(request.args[\"code\"], redirect_uri=REDIRECT_URI,   # constant from config\n                 code_verifier=session.pop(\"code_verifier\"))\nclaims = verify_jwt(token[\"id_token\"], jwks, iss=ISSUER, aud=CLIENT_ID,\n                    nonce=session.pop(\"oauth_nonce\"))"
   },
   "sources": [
    "https://www.rfc-editor.org/rfc/rfc9700.html",
    "https://cheatsheetseries.owasp.org/cheatsheets/OAuth2_Cheat_Sheet.html",
    "https://openid.net/specs/openid-connect-core-1_0.html",
    "https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow",
    "https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html",
    "https://developers.google.com/identity/gsi/web/guides/verify-google-id-token"
   ],
   "orgs": [
    {
     "id": "ietf",
     "name": "IETF",
     "sources": [
      "https://www.rfc-editor.org/rfc/rfc9700.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/OAuth2_Cheat_Sheet.html",
      "https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "openid",
     "name": "OpenID Foundation",
     "sources": [
      "https://openid.net/specs/openid-connect-core-1_0.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://developers.google.com/identity/gsi/web/guides/verify-google-id-token"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-oauth-code-flow-binding",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-password-reset",
   "level": "service",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "authn",
    "passwords"
   ],
   "lang": "core",
   "applies_when": "The service emails or texts a password-reset link or code.",
   "context": [
    "server"
   ],
   "tool_gate": null,
   "note": "single-use - OWASP, Auth0, Django; short-lived - OWASP, Auth0, NIST; revoke sessions - MSRC, Auth0, Django, with OWASP leaving it optional. Storing only a hash of the token (OWASP and NIST for recovery codes) and revoking other outstanding reset links (OWASP, after suspected compromise) have too few sources, so they are in Why. The practical gate is a test that redeems a token twice, redeems an expired one, and then replays an old session cookie; the request endpoint's reply and rate limit belong to svc-auth-throttle-generic-errors",
   "lacks": null,
   "rule": "Make the reset token single-use and short-lived, and on success revoke every session.",
   "why": "A reset link is a bearer password that arrives in a mailbox. If it works twice, a forwarded mail, a mail scanner or a backup keeps the access alive. If it lives for days, an attacker has days to use it. And a reset that leaves the old sessions open changes the password without evicting the intruder who already had a session. Two further habits cost little: store only a hash of the token, so a database read or a backup hands over no working links, and revoke the other outstanding reset links on success.",
   "counterpoint": "\"The token is random, so hashing it adds nothing.\" The mail, the link and every log between you and the user still carry the raw value; the hash protects the copy at rest, which is the copy an attacker reads. OWASP treats session invalidation as the user's choice in the normal flow and requires it after suspected compromise; this card asks for it every time because the reset may be the owner's answer to a stolen session. Pick the lifetime from a number the support flow can meet, not from the word \"short\", and send a notification after the reset so a change the owner did not make is visible.",
   "fail": {
    "language": "text",
    "code": "token = f\"{user.id}-{int(time.time())}\"\nuser.reset_token = token             # predictable, stored in clear, usable again\nsend_email(link(token))"
   },
   "pass": {
    "language": "python",
    "code": "raw = secrets.token_urlsafe(32)\ndb.store(sha256(raw), user=user, expires=now + 15 * MINUTE)\n# the raw value exists only in the mail\nconsume_once(raw)\nrevoke_sessions(user)\nrevoke_other_resets(user)"
   },
   "sources": [
    "https://cheatsheetseries.owasp.org/cheatsheets/Forgot_Password_Cheat_Sheet.html",
    "https://pages.nist.gov/800-63-4/sp800-63b/events/",
    "https://auth0.com/docs/authenticate/database-connections/password-change",
    "https://www.microsoft.com/en-us/msrc/blog/2022/05/pre-hijacking-attacks",
    "https://docs.djangoproject.com/en/5.2/topics/auth/default/"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Forgot_Password_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nist",
     "name": "NIST",
     "sources": [
      "https://pages.nist.gov/800-63-4/sp800-63b/events/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "auth0",
     "name": "Okta (Auth0)",
     "sources": [
      "https://auth0.com/docs/authenticate/database-connections/password-change"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://www.microsoft.com/en-us/msrc/blog/2022/05/pre-hijacking-attacks"
     ],
     "strength": "unjudged"
    },
    {
     "id": "django",
     "name": "Django Software Foundation",
     "sources": [
      "https://docs.djangoproject.com/en/5.2/topics/auth/default/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-password-reset",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-rate-limit-quota",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "security",
    "reliability"
   ],
   "topics": [
    "multi-tenant",
    "overload"
   ],
   "lang": "core",
   "applies_when": "A shared API or queue serves several tenants or callers.",
   "context": [
    "server"
   ],
   "tool_gate": null,
   "note": "the per-tenant cap rests on AWS (quotas at a per-tenant or per-workload granularity), Google (a quota for each customer) and Microsoft (quota enforcement so a single tenant cannot overwhelm the system); GitHub, Shopify, AWS and Microsoft document the principal key and the window",
   "lacks": null,
   "rule": "Enforce a per-tenant quota on shared APIs and queues.",
   "why": "Without a quota, one noisy tenant consumes shared capacity for everyone. Key the count to the identity that owns the quota, such as the tenant, account or API key, and state the window and when capacity returns. Also add a per-tenant concurrency cap, and return 429 for over-quota but 503 for the service's own lack of capacity, so clients do not retry quota errors as capacity problems.",
   "counterpoint": "\"One global rate limit is simpler.\" A global limit protects the service, but one tenant can use all of it, and then every other tenant gets errors. A per-tenant limit costs one key per tenant in the limiter. Some older services return 503 for quota errors, so the status code is a convention. What matters is that a client can tell a quota error from a capacity error. Shuffle sharding (per-tenant worker subsets) isolates tenants further, and this card does not require it.",
   "fail": {
    "language": "text",
    "code": "every tenant shares one worker pool and one request budget\nno per-tenant limit: one tenant's batch job starves the rest"
   },
   "pass": {
    "language": "text",
    "code": "limiter.check(tenant_id) -> 429 + Retry-After\nper-tenant semaphore on workers"
   },
   "sources": [
    "https://aws.amazon.com/builders-library/fairness-in-multi-tenant-systems/",
    "https://aws.amazon.com/builders-library/workload-isolation-using-shuffle-sharding/",
    "https://sre.google/sre-book/handling-overload/",
    "https://learn.microsoft.com/en-us/azure/architecture/antipatterns/noisy-neighbor/noisy-neighbor",
    "https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-228-upd1.pdf",
    "https://docs.github.com/en/graphql/overview/rate-limits-and-query-limits-for-the-graphql-api",
    "https://shopify.dev/docs/apps/build/apis/graphql-admin/rate-limits",
    "https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-throttling.html",
    "https://learn.microsoft.com/en-us/azure/api-management/rate-limit-policy"
   ],
   "orgs": [
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://aws.amazon.com/builders-library/fairness-in-multi-tenant-systems/",
      "https://aws.amazon.com/builders-library/workload-isolation-using-shuffle-sharding/",
      "https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-throttling.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://sre.google/sre-book/handling-overload/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/architecture/antipatterns/noisy-neighbor/noisy-neighbor",
      "https://learn.microsoft.com/en-us/azure/api-management/rate-limit-policy"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nist",
     "name": "NIST",
     "sources": [
      "https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-228-upd1.pdf"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://docs.github.com/en/graphql/overview/rate-limits-and-query-limits-for-the-graphql-api"
     ],
     "strength": "unjudged"
    },
    {
     "id": "shopify",
     "name": "Shopify",
     "sources": [
      "https://shopify.dev/docs/apps/build/apis/graphql-admin/rate-limits"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-rate-limit-quota",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-reflow-and-zoom",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "accessibility",
    "usability"
   ],
   "topics": [
    "frontend",
    "css",
    "responsive"
   ],
   "lang": "core",
   "applies_when": "A page or app is served to browsers on screens of any size.",
   "context": [
    "ui"
   ],
   "tool_gate": "axe `meta-viewport`; a rendered check at a 320 px viewport and at 200% text zoom.",
   "note": "The 320 px width and no sideways scrolling are backed by W3C, WebAIM and Microsoft; WebAIM partly restates the WCAG wording but adds its own zoom test, and Microsoft states only a 320 px design floor. 200% text zoom is backed by W3C, A11y Project, Mozilla and Google. No blocked pinch-zoom is backed by Deque, A11y Project and Mozilla, and Google adds a fourth voice. The A11y Project checklist asks for zoom left on, text at 200%, and no horizontal scrolling",
   "lacks": null,
   "rule": "Make the page usable at 320 CSS pixels wide and at 200% text zoom, with no sideways scrolling, no clipped content, and no blocked pinch-zoom.",
   "why": "Low-vision users enlarge the page instead of using a small screen. A fixed width or a clipped container hides content from exactly those users. `user-scalable=no` takes away their only tool.",
   "counterpoint": "\"Maps and data tables need two-way scrolling.\" They are exempt, but only the part that needs it. Wrap the table in its own scroll box, and keep the headings and controls around it reflowing.",
   "fail": {
    "language": "html",
    "code": "<meta name=\"viewport\" content=\"width=device-width, user-scalable=no, maximum-scale=1\">"
   },
   "pass": {
    "language": "html",
    "code": "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
   },
   "sources": [
    "https://www.w3.org/WAI/WCAG22/Understanding/reflow.html",
    "https://www.w3.org/WAI/WCAG22/Understanding/resize-text.html",
    "https://dequeuniversity.com/rules/axe/html/4.10/meta-viewport",
    "https://www.w3.org/WAI/test-evaluate/preliminary/",
    "https://www.a11yproject.com/checklist/",
    "https://webaim.org/techniques/reflow/",
    "https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/meta/name/viewport",
    "https://web.dev/articles/accessible-responsive-design",
    "https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/design/design-teams-app-layout-scaling"
   ],
   "orgs": [
    {
     "id": "w3c",
     "name": "W3C",
     "sources": [
      "https://www.w3.org/WAI/WCAG22/Understanding/reflow.html",
      "https://www.w3.org/WAI/WCAG22/Understanding/resize-text.html",
      "https://www.w3.org/WAI/test-evaluate/preliminary/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "deque",
     "name": "Deque",
     "sources": [
      "https://dequeuniversity.com/rules/axe/html/4.10/meta-viewport"
     ],
     "strength": "unjudged"
    },
    {
     "id": "a11yproject",
     "name": "The A11y Project",
     "sources": [
      "https://www.a11yproject.com/checklist/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "webaim",
     "name": "WebAIM",
     "sources": [
      "https://webaim.org/techniques/reflow/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/meta/name/viewport"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://web.dev/articles/accessible-responsive-design"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/design/design-teams-app-layout-scaling"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-ui#svc-reflow-and-zoom",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-request-framing",
   "level": "service",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "http",
    "protocol"
   ],
   "lang": "core",
   "applies_when": "The service parses HTTP requests itself, or receives them through a proxy, load balancer, or CDN.",
   "context": [
    "server"
   ],
   "tool_gate": "A test that sends one request with both Content-Length and Transfer-Encoding and expects 400 or a closed connection.",
   "note": "reject or remove one framing field - IETF, PortSwigger, Microsoft, Fastly, Envoy and Cloudflare; strict parsing - IETF, OpenJS (Node), PortSwigger, Envoy and Microsoft; the HTTP/1.1 downgrade rule has PortSwigger and Fastly only, so it is in Why",
   "lacks": null,
   "rule": "Reject a request that carries both `Transfer-Encoding` and `Content-Length`, or remove one field at the first hop, and parse header framing strictly rather than with a leniency flag.",
   "why": "Two hops can disagree about where a request ends. The front end reads one body length and the back end reads another, so the leftover bytes become the start of the next request on the same connection. That request skips the front end's checks: it can reach an admin route, poison a cache, or splice one user's response into another user's connection. The ambiguity comes from a second framing field, a malformed header name, or a parser that accepts what the specification rejects. A front end that downgrades HTTP/2 to HTTP/1.1 reopens the gap, so validate the rewritten request as well.",
   "counterpoint": "\"We speak HTTP/2 end to end, so there is nothing to smuggle.\" HTTP/2 has one framing mechanism, but many deployments still downgrade at the last hop, and compatibility flags keep the old leniency in parsers. Check both ends of every connection and the rewrite between them, not the version the client negotiated.",
   "fail": {
    "language": "text",
    "code": "POST /login HTTP/1.1\nHost: app.example\nContent-Length: 15\nTransfer-Encoding: chunked\n\n0\n\nGET /admin"
   },
   "pass": {
    "language": "text",
    "code": "# front end\nif both framing fields are present:\n    return 400 and close the connection      # or drop Content-Length, forward, then close\n\n# parser\ninsecureHTTPParser: false                    # Node\nallow_chunked_length: false                  # Envoy\nwhitespace before a header colon: 400"
   },
   "sources": [
    "https://www.rfc-editor.org/rfc/rfc9112.html",
    "https://portswigger.net/web-security/request-smuggling",
    "https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel/security-considerations?view=aspnetcore-10.0",
    "https://www.fastly.com/learning/security/what-is-http-request-smuggling",
    "https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/http/header_validators/envoy_default/v3/header_validator.proto.html",
    "https://developers.cloudflare.com/support/troubleshooting/http-status-codes/4xx-client-error/error-400/",
    "https://nodejs.org/api/http.html"
   ],
   "orgs": [
    {
     "id": "ietf",
     "name": "IETF",
     "sources": [
      "https://www.rfc-editor.org/rfc/rfc9112.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "portswigger",
     "name": "PortSwigger",
     "sources": [
      "https://portswigger.net/web-security/request-smuggling"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel/security-considerations?view=aspnetcore-10.0"
     ],
     "strength": "unjudged"
    },
    {
     "id": "fastly",
     "name": "Fastly",
     "sources": [
      "https://www.fastly.com/learning/security/what-is-http-request-smuggling"
     ],
     "strength": "unjudged"
    },
    {
     "id": "envoy",
     "name": "Envoy",
     "sources": [
      "https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/http/header_validators/envoy_default/v3/header_validator.proto.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cloudflare",
     "name": "Cloudflare",
     "sources": [
      "https://developers.cloudflare.com/support/troubleshooting/http-status-codes/4xx-client-error/error-400/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nodejs",
     "name": "OpenJS Foundation",
     "sources": [
      "https://nodejs.org/api/http.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-request-framing",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-retry-policy",
   "level": "service",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "reliability"
   ],
   "topics": [
    "resilience",
    "retries"
   ],
   "lang": "core",
   "applies_when": "The code retries a failed call, or wraps a client that retries.",
   "context": [],
   "tool_gate": null,
   "note": "Transient errors are backed by Microsoft, Google SRE and AWS (a vendor), though AWS frames retries as the tool for transient failures rather than forbidding permanent ones. Capped attempts are backed by Microsoft, Google SRE, AWS and GitHub (a vendor). Jittered exponential backoff is backed by Google SRE, AWS and Stripe (a vendor). Google SRE gives a retry budget (about 10% of requests); the card leaves the number to the team",
   "lacks": null,
   "rule": "Retry only transient errors, with capped attempts and jittered exponential backoff.",
   "why": "Immediate, unbounded, or synchronized retries turn a brief fault into a load spike on the struggling dependency. Retry at one layer only: three layers of three attempts is nine calls per user request. Make retried writes safe with [[svc-idempotency-key]].",
   "counterpoint": "\"Retries make us more reliable.\" Only for transient faults and only within a budget. Past that they hurt, so a non-transient error should fail at once. A retry budget (retries capped as a share of traffic) helps further. This card leaves its size to the team.",
   "fail": {
    "language": "text",
    "code": "while True:\n    try: return client.call()\n    except Exception: sleep(1)        # no cap, no jitter, retries 4xx too"
   },
   "pass": {
    "language": "python",
    "code": "import random, time\nfor i in range(3):\n    try:\n        return client.call()\n    except TransientError:\n        if i == 2:\n            raise\n        time.sleep(random.uniform(0, min(CAP_S, BASE_S * 2**i)))\n# retry at one layer only: the client's own retries are off"
   },
   "sources": [
    "https://learn.microsoft.com/en-us/azure/architecture/patterns/retry",
    "https://aws.amazon.com/builders-library/minimizing-correlated-failures-in-distributed-systems/",
    "https://sre.google/sre-book/addressing-cascading-failures/",
    "https://sre.google/sre-book/handling-overload/",
    "https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/",
    "https://docs.stripe.com/rate-limits",
    "https://docs.github.com/en/rest/using-the-rest-api/best-practices-for-using-the-rest-api"
   ],
   "orgs": [
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/architecture/patterns/retry"
     ],
     "strength": "unjudged"
    },
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://aws.amazon.com/builders-library/minimizing-correlated-failures-in-distributed-systems/",
      "https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://sre.google/sre-book/addressing-cascading-failures/",
      "https://sre.google/sre-book/handling-overload/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "stripe",
     "name": "Stripe",
     "sources": [
      "https://docs.stripe.com/rate-limits"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://docs.github.com/en/rest/using-the-rest-api/best-practices-for-using-the-rest-api"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-any#svc-retry-policy",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-saga-steps",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "reliability",
    "correctness"
   ],
   "topics": [
    "distributed",
    "consistency"
   ],
   "lang": "core",
   "applies_when": "A business operation spans services, each with its own database, coordinated as a saga.",
   "context": [
    "server"
   ],
   "tool_gate": null,
   "note": null,
   "lacks": null,
   "rule": "Make steps after the pivot idempotent and retryable, and make every compensation idempotent with a failure path.",
   "why": "After the pivot there is no undo, only retry until done. A compensation can itself fail, and unhandled it leaves the system inconsistent with nobody told.",
   "counterpoint": "\"Use a distributed transaction.\" Often unavailable across services. A saga has no isolation, so also guard against reading half-done data (semantic locks, re-reading values before acting). [[svc-idempotency-key]] covers keys, and [[svc-dlq]] covers failed messages.",
   "fail": {
    "language": "text",
    "code": "step 4 (after pivot): charge_card(order)     # retried -> double charge\ncompensate: release_stock(order)             # throws; exception swallowed"
   },
   "pass": {
    "language": "text",
    "code": "step 4: charge_card(order, idempotency_key=order.id)   # safe to retry\ncompensate: release_stock(order) retried; on exhaustion -> alert + manual queue"
   },
   "sources": [
    "https://learn.microsoft.com/en-us/azure/architecture/patterns/saga",
    "https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/saga-choreography.html",
    "https://docs.temporal.io/design-patterns/saga-pattern"
   ],
   "orgs": [
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/architecture/patterns/saga"
     ],
     "strength": "unjudged"
    },
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/saga-choreography.html"
     ],
     "strength": "supports"
    },
    {
     "id": "temporal",
     "name": "Temporal",
     "sources": [
      "https://docs.temporal.io/design-patterns/saga-pattern"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/service-server#svc-saga-steps",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-saml-assertion-binding",
   "level": "service",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "authn",
    "saml"
   ],
   "lang": "core",
   "applies_when": "The service is a SAML 2.0 service provider and accepts a response or assertion at an assertion consumer service endpoint.",
   "context": [
    "server"
   ],
   "tool_gate": null,
   "note": "fn-xml-no-external-entities covers the XML parser; this card covers what to check before a NameID becomes a session. For an IdP-initiated response, `InResponseTo` must be absent; keep the replay cache.",
   "lacks": null,
   "rule": "Verify the XML signature over the assertion you read, with the IdP's configured key rather than a key carried in the response, then require the assertion's `Audience` and its bearer `SubjectConfirmationData` `Recipient` to name this SP, the response `Destination` to be this endpoint, `InResponseTo` to match your request (absent for an IdP-initiated response), `NotOnOrAfter` to be current, and the assertion ID to be unseen. These are one step, verify the assertion, and each check is cited.",
   "why": "A valid signature elsewhere in the document proves nothing about the `<NameID>` your code just read. That gap is the signature-wrapping attack, and it lets an attacker choose which identity your service trusts. A lookup such as `.//NameID` is how the signed element and the read element get separated. Pinning the key to the IdP stops the document choosing its own verification key. The audience and destination checks stop an assertion minted for another SP from being posted here. `InResponseTo` catches a response injected into a browser that never started the request, and the time window plus the used-ID set stop a captured assertion from being replayed.",
   "counterpoint": "\"The library validates the signature, so the assertion is trustworthy.\" The signature vouches for the bytes it covers, not for the element your handler reads. A strict mode that validates the response but leaves an unsigned assertion in the tree still passes a wrapped `NameID`. Require the assertion to be inside the signed content, take the key from IdP metadata, and record the ID of the assertion you accepted, not the ID of its wrapper.",
   "fail": {
    "language": "text",
    "code": "doc = etree.fromstring(base64.b64decode(form[\"SAMLResponse\"]))\nname_id = doc.find(\".//NameID\").text\nlogin(name_id)      # no signature check, no audience, no replay set"
   },
   "pass": {
    "language": "python",
    "code": "signed = verify_signature(response, key=IDP_CERT)     # returns the signed element\nassertion = signed.assertion\nif assertion.audience != SP_ENTITY_ID or response.destination != ACS_URL:\n    raise InvalidAssertion\nif assertion.subject_confirmation.recipient != ACS_URL:\n    raise InvalidAssertion\nif assertion.subject_confirmation.in_response_to != request_id:\n    raise InvalidAssertion\nif assertion.not_on_or_after <= now() or used_ids.seen(assertion.id, until=assertion.not_on_or_after):\n    raise InvalidAssertion"
   },
   "sources": [
    "https://docs.oasis-open.org/security/saml/v2.0/saml-profiles-2.0-os.pdf",
    "https://cheatsheetseries.owasp.org/cheatsheets/SAML_Security_Cheat_Sheet.html",
    "https://pages.nist.gov/800-63-4/sp800-63c/Federation/",
    "https://learn.microsoft.com/en-us/entra/identity-platform/single-sign-on-saml-protocol",
    "https://github.com/SAML-Toolkits/python3-saml"
   ],
   "orgs": [
    {
     "id": "oasis",
     "name": "OASIS",
     "sources": [
      "https://docs.oasis-open.org/security/saml/v2.0/saml-profiles-2.0-os.pdf"
     ],
     "strength": "unjudged"
    },
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/SAML_Security_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nist",
     "name": "NIST",
     "sources": [
      "https://pages.nist.gov/800-63-4/sp800-63c/Federation/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/entra/identity-platform/single-sign-on-saml-protocol"
     ],
     "strength": "unjudged"
    },
    {
     "id": "saml-toolkits",
     "name": "SAML-Toolkits project",
     "sources": [
      "https://github.com/SAML-Toolkits/python3-saml"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-saml-assertion-binding",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-session-lifecycle",
   "level": "service",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "authn",
    "sessions"
   ],
   "lang": "core",
   "applies_when": "The service sets a session cookie or session ID after login.",
   "context": [
    "server"
   ],
   "tool_gate": "Header test on the login response; session fixation test in the end-to-end suite.",
   "note": null,
   "lacks": null,
   "rule": "Issue a new session ID at login, and set `Secure`, `HttpOnly` and `SameSite` on the session cookie.",
   "why": "A session ID that survives login can be planted by an attacker beforehand (fixation). A cookie without `HttpOnly` is readable by any injected script, and one without `Secure` travels over plain HTTP. Also end the session on the server at logout, because clearing only the browser cookie leaves a stolen copy valid, and enforce idle and absolute timeouts on the server. Scope the cookie to one host. With `Domain` omitted it is returned only to the host that set it, while a `Domain` set to the registrable domain sends it to every subdomain. Omitting `Domain` does not stop planting: a sibling subdomain that serves user content can still set a parent-domain cookie with the same name for the parent to accept. The `__Host-` prefix is what stops that, because the browser refuses a `__Host-` cookie that carries `Domain` or a path other than `/`. Use it on the session cookie.",
   "counterpoint": "\"The framework already does this.\" Some do: Spring Security changes the session ID at login by default, and Rails gives you `reset_session`. Hand-written login code and custom session stores often skip it, and cookie flags depend on configuration. Check the login handler and the real `Set-Cookie` header, not the framework's reputation.",
   "fail": {
    "language": "text",
    "code": "Set-Cookie: sid=abc123\nlogin():  keep the pre-login session id"
   },
   "pass": {
    "language": "text",
    "code": "Set-Cookie: __Host-sid=<new random>; Secure; HttpOnly; SameSite=Lax; Path=/   # the __Host- name is optional hardening, not a Rule clause\nlogin():  session.regenerate()"
   },
   "sources": [
    "https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html",
    "https://top10.owasp.org/2025/A01_2025-Broken_Access_Control",
    "https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/Cookies",
    "https://pages.nist.gov/800-63-4/sp800-63b/session/",
    "https://docs.spring.io/spring-security/reference/servlet/authentication/session-management.html",
    "https://guides.rubyonrails.org/security.html"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html",
      "https://top10.owasp.org/2025/A01_2025-Broken_Access_Control"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/Cookies"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nist",
     "name": "NIST",
     "sources": [
      "https://pages.nist.gov/800-63-4/sp800-63b/session/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "spring",
     "name": "Spring",
     "sources": [
      "https://docs.spring.io/spring-security/reference/servlet/authentication/session-management.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "rails",
     "name": "Ruby on Rails",
     "sources": [
      "https://guides.rubyonrails.org/security.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-session-lifecycle",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-slow-operation-shows-progress",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "usability",
    "accessibility"
   ],
   "topics": [
    "frontend"
   ],
   "lang": "core",
   "applies_when": "A page action, such as a save, an upload, a search, or a page load, can take more than about a second.",
   "context": [
    "ui"
   ],
   "tool_gate": "none; throttle the network and run the action.",
   "note": "Showing that a slow action is under way is backed by NN/g, Apple, Microsoft and Atlassian. Showing progress when it runs long is backed by NN/g, Apple and Microsoft, and Microsoft gives the determinate and indeterminate split the PASS example uses. The 1 s and 10 s limits and the Cancel button come from NN/g, and Cancel also from Apple, so they are in Why. Norman's feedback principle is topic only, and Shneiderman's golden rules are a person's list, not an organisation",
   "lacks": null,
   "rule": "Show that a slow action is under way, and show its progress when it runs long.",
   "why": "With no feedback, users click again, reload, or leave, and a double click can run the action twice. A bar tells them whether to wait. A message that says what is happening is better than \"Loading\". As a guide, show feedback after about one second. Past about ten seconds, show how much is done and offer Cancel where stopping is safe.",
   "counterpoint": "\"A spinner on a 200 ms call flickers.\" Right: below about a second, show nothing, or the placeholder just flashes. Skeleton screens suit page loads, and progress bars suit uploads and other long jobs.",
   "fail": {
    "language": "jsx",
    "code": "<button onClick={upload}>Upload</button>   // 40 s, nothing changes"
   },
   "pass": {
    "language": "jsx",
    "code": "<button onClick={busy ? undefined : upload} aria-disabled={busy}>Upload</button>\n{busy && <progress value={sent} max={total} aria-label=\"Uploading report.pdf\" />}\n{busy && <button onClick={cancel}>Cancel</button>}\n<div role=\"status\">{message}</div>   // \"Uploading report.pdf\", then \"Upload complete\"\n// aria-disabled keeps keyboard focus on the button; disabled would drop it"
   },
   "sources": [
    "https://www.nngroup.com/articles/response-times-3-important-limits/",
    "https://www.nngroup.com/articles/skeleton-screens/",
    "https://developer.apple.com/tutorials/data/design/human-interface-guidelines/progress-indicators.json",
    "https://developer.apple.com/tutorials/data/design/human-interface-guidelines/feedback.json",
    "https://www.cs.umd.edu/users/ben/goldenrules.html",
    "https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html",
    "https://learn.microsoft.com/en-us/windows/apps/design/controls/progress-controls",
    "https://atlassian.design/components/spinner/usage"
   ],
   "orgs": [
    {
     "id": "nngroup",
     "name": "Nielsen Norman Group",
     "sources": [
      "https://www.nngroup.com/articles/response-times-3-important-limits/",
      "https://www.nngroup.com/articles/skeleton-screens/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "apple",
     "name": "Apple",
     "sources": [
      "https://developer.apple.com/tutorials/data/design/human-interface-guidelines/progress-indicators.json",
      "https://developer.apple.com/tutorials/data/design/human-interface-guidelines/feedback.json"
     ],
     "strength": "unjudged"
    },
    {
     "id": "w3c",
     "name": "W3C",
     "sources": [
      "https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/windows/apps/design/controls/progress-controls"
     ],
     "strength": "unjudged"
    },
    {
     "id": "atlassian",
     "name": "Atlassian",
     "sources": [
      "https://atlassian.design/components/spinner/usage"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "shneiderman",
     "name": "Ben Shneiderman",
     "sources": [
      "https://www.cs.umd.edu/users/ben/goldenrules.html"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-ui#svc-slow-operation-shows-progress",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-soft-delete-default",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "security",
    "correctness"
   ],
   "topics": [
    "data"
   ],
   "lang": "core",
   "applies_when": "A table or model marks a row as deleted instead of removing it.",
   "context": [
    "data"
   ],
   "tool_gate": "A test per read path: soft-delete a row, then assert the endpoint returns 404 or leaves it out of the list.",
   "note": null,
   "lacks": null,
   "rule": "Filter soft-deleted rows out of every read path by default, and make a read that includes them an explicit call.",
   "why": "A soft delete sets a column. It does not remove the row, so any query that forgets the filter still returns the record. One raw query or one new endpoint is enough to expose it. Put the filter in the model's default scope, so new queries inherit it, and keep the escape hatch visible. A deletion policy has to reach caches, exports, replicas and object versions too, or the record lives on in a copy.",
   "counterpoint": "\"The admin console has to see deleted rows.\" Keep that one call explicit, name it for what it does, and check the caller is an admin. The default stays closed. Raw SQL and reporting replicas are the usual bypass, so route them through the same helper or audit them. A service that hard-deletes does not need the filter, but backups and object versions can still hold the old copy.",
   "fail": {
    "language": "csharp",
    "code": "modelBuilder.Entity<Document>();                 // no HasQueryFilter\nvar doc = await context.Documents.FindAsync(42); // returns the soft-deleted row"
   },
   "pass": {
    "language": "csharp",
    "code": "modelBuilder.Entity<Document>().HasQueryFilter(d => d.DeletedAt == null);\n\n// The one admin report that needs deleted rows, behind an admin check:\nvar all = await context.Documents.IgnoreQueryFilters().ToListAsync();"
   },
   "sources": [
    "https://learn.microsoft.com/en-us/ef/core/querying/filters",
    "https://laravel.com/docs/12.x/eloquent",
    "https://sequelize.org/docs/v6/core-concepts/paranoid/",
    "https://cheatsheetseries.owasp.org/cheatsheets/Multi_Tenant_Security_Cheat_Sheet.html"
   ],
   "orgs": [
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/ef/core/querying/filters"
     ],
     "strength": "unjudged"
    },
    {
     "id": "laravel",
     "name": "Laravel",
     "sources": [
      "https://laravel.com/docs/12.x/eloquent"
     ],
     "strength": "unjudged"
    },
    {
     "id": "sequelize",
     "name": "Sequelize",
     "sources": [
      "https://sequelize.org/docs/v6/core-concepts/paranoid/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Multi_Tenant_Security_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-data#svc-soft-delete-default",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-sqli-authn",
   "level": "service",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "injection",
    "sql"
   ],
   "lang": "core",
   "applies_when": "The code builds a SQL query that includes request data or other untrusted input.",
   "context": [
    "server",
    "data"
   ],
   "tool_gate": "Bandit B608 (`hardcoded_sql_expressions`), CodeQL `py/sql-injection`.",
   "note": null,
   "lacks": null,
   "rule": "Pass untrusted data to queries as bound parameters, never by string concatenation or formatting.",
   "why": "Concatenated input becomes SQL, so a value of `1 OR 1=1` reads every row and a quote ends the string early. A bound parameter travels apart from the query text, and the database never parses it as SQL.",
   "counterpoint": "\"An ORM makes this moot.\" Raw fragments still bypass it, and so do table, column and `ORDER BY` names, which cannot be bound. Map those to a fixed allowlist in code.",
   "fail": {
    "language": "python",
    "code": "db.execute(\"SELECT * FROM users WHERE id = \" + req.id)\ndb.execute(f\"SELECT * FROM users WHERE name = '{name}'\")"
   },
   "pass": {
    "language": "python",
    "code": "db.execute(\"SELECT * FROM users WHERE id = ?\", (req.id,))     # the placeholder style depends on the driver\ndb.execute(\"SELECT * FROM users WHERE name = ?\", (name,))"
   },
   "sources": [
    "https://owasp.org/Top10/2025/",
    "https://docs.python.org/3/library/sqlite3.html",
    "https://codeql.github.com/codeql-query-help/python/py-sql-injection/",
    "https://portswigger.net/web-security/sql-injection",
    "https://go.dev/doc/database/sql-injection"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://owasp.org/Top10/2025/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://docs.python.org/3/library/sqlite3.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://codeql.github.com/codeql-query-help/python/py-sql-injection/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "portswigger",
     "name": "PortSwigger",
     "sources": [
      "https://portswigger.net/web-security/sql-injection"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://go.dev/doc/database/sql-injection"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-sqli-authn",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-stream-dont-materialise",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "performance"
   ],
   "topics": [
    "memory",
    "io"
   ],
   "lang": "core",
   "applies_when": "A handler reads, transforms, or returns a result whose size depends on the data (files, query results, response bodies).",
   "context": [],
   "tool_gate": "Memory limit in tests with a large fixture.",
   "note": "Node-specific source, general claim; the unbounded-background-tasks half of the gap has no source and is covered by sys-load-shedding-bounded-queues",
   "lacks": null,
   "rule": "Process large results as a stream with backpressure; do not load them whole into memory.",
   "why": "A fast source piles data into memory faster than a slow consumer drains it. In the Node docs' test, memory grew from about 88 MB to about 1.5 GB. Memory then scales with data size, not with the code.",
   "counterpoint": "\"Streaming complicates errors and retries.\" Yes: a failure mid-stream cannot change the status code. For small, bounded results, loading the whole result is simpler and fine; state the bound. [[sys-load-shedding-bounded-queues]] covers queue limits.",
   "fail": {
    "language": "python",
    "code": "rows = db.execute(\"SELECT * FROM events\").fetchall()   # 50M rows\nreturn json.dumps(rows)"
   },
   "pass": {
    "language": "python",
    "code": "def generate():\n    for row in db.execute(\"SELECT * FROM events\"):     # server-side cursor\n        yield encode(row)\nreturn StreamingResponse(generate())"
   },
   "sources": [
    "https://nodejs.org/en/learn/modules/backpressuring-in-streams",
    "https://jdbc.postgresql.org/documentation/query/",
    "https://www.reactive-streams.org/"
   ],
   "orgs": [
    {
     "id": "nodejs",
     "name": "OpenJS Foundation",
     "sources": [
      "https://nodejs.org/en/learn/modules/backpressuring-in-streams"
     ],
     "strength": "unjudged"
    },
    {
     "id": "postgresql",
     "name": "PostgreSQL",
     "sources": [
      "https://jdbc.postgresql.org/documentation/query/"
     ],
     "strength": "supports"
    },
    {
     "id": "reactive-streams",
     "name": "Reactive Streams",
     "sources": [
      "https://www.reactive-streams.org/"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/service-any#svc-stream-dont-materialise",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-tenant-id-in-telemetry",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "security",
    "observability"
   ],
   "topics": [
    "multi-tenant"
   ],
   "lang": "core",
   "applies_when": "A shared log, metric or audit store holds records from more than one tenant.",
   "context": [
    "server"
   ],
   "tool_gate": "A test that writes an event for each of two tenants, then queries one tenant and asserts the other is absent.",
   "note": "tenant id on tenant-scoped records backed by OWASP (server-verified tenant context in security and audit events), AWS SaaS Lens (inject tenant context into log messages and metric events) and Microsoft (a telemetry initializer injects the tenant ID into every telemetry item; the noisy-neighbor page asks for the tenant identifier in telemetry); Google GKE routes logs to a store per tenant, which is the alternative in the Counterpoint",
   "lacks": null,
   "rule": "Put the verified tenant id on every tenant-scoped log, metric and audit record.",
   "why": "A shared store returns another tenant's activity to any query that does not filter. The id has to be written from the verified tenant context, not from a request header, and it has to be there when a batch job or a queue worker writes the record, since no request middleware runs in those paths. A centralized audit store is acceptable only when read access enforces tenant scope. Metric events need the field too, or a per-tenant dashboard shows another tenant's numbers. Platform events that belong to no tenant, such as a node restart, carry no tenant id and are outside the Rule.",
   "counterpoint": "\"The pipeline adds the tenant from the request context.\" That works only for code that runs inside a request. Workers, schedulers and sidecars write records too, and a query cannot recover a field that was never written. If a shared layer injects the field, verify it with a two-tenant query, because a layer that silently skips one producer looks the same as a correct one until the query runs. Splitting logs into a store per tenant is stronger still where the operational cost is justified.",
   "fail": {
    "language": "python",
    "code": "log.info(\"order %s paid\", order_id)\naudit.write(action=\"order.paid\", actor=user.id)"
   },
   "pass": {
    "language": "python",
    "code": "log.info(\"order %s paid\", order_id, extra={\"tenant_id\": tenant.id})\naudit.write(action=\"order.paid\", tenant_id=tenant.id, actor=user.id)"
   },
   "sources": [
    "https://cheatsheetseries.owasp.org/cheatsheets/Multi_Tenant_Security_Cheat_Sheet.html",
    "https://docs.aws.amazon.com/wellarchitected/latest/saas-lens/layers-hide-tenant-details.html",
    "https://learn.microsoft.com/en-us/azure/architecture/guide/multitenant/service/application-insights",
    "https://learn.microsoft.com/en-us/azure/architecture/antipatterns/noisy-neighbor/noisy-neighbor",
    "https://docs.cloud.google.com/kubernetes-engine/docs/how-to/multi-tenant-logging",
    "https://learn.microsoft.com/en-us/azure/architecture/guide/multitenant/approaches/storage-data"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Multi_Tenant_Security_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://docs.aws.amazon.com/wellarchitected/latest/saas-lens/layers-hide-tenant-details.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/architecture/guide/multitenant/service/application-insights",
      "https://learn.microsoft.com/en-us/azure/architecture/antipatterns/noisy-neighbor/noisy-neighbor",
      "https://learn.microsoft.com/en-us/azure/architecture/guide/multitenant/approaches/storage-data"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://docs.cloud.google.com/kubernetes-engine/docs/how-to/multi-tenant-logging"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-tenant-id-in-telemetry",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-timeouts",
   "level": "service",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "performance",
    "reliability"
   ],
   "topics": [
    "resilience",
    "io"
   ],
   "lang": "core",
   "applies_when": "The code calls a remote service over a network.",
   "context": [],
   "tool_gate": null,
   "note": "Rule backed by AWS, Google (gRPC), and Microsoft (ASP.NET Core gRPC); deriving the timeout from the caller's deadline has 2 orgs (Google, Microsoft) and sits in Why; no source prescribes timeout numbers",
   "lacks": null,
   "rule": "Set an explicit timeout on every remote call.",
   "why": "A call without a bound holds a thread, a connection, and a caller indefinitely, so one slow dependency becomes an outage. Many clients, gRPC and `requests` among them, set no timeout by default. Where the caller has a deadline of its own, pass the remaining time down: a per-hop timeout that ignores it keeps working for a client that already gave up, and a retry loop shares that one deadline across its attempts.",
   "counterpoint": "\"Defaults are fine; the library will time out eventually.\" Many defaults are infinite, and an eventual timeout longer than the caller's own deadline is wasted work. Configure the client's timeout once at construction, connect and read separately, then shrink it per call to what the caller has left. Pick the numbers from measured latency, such as the downstream p99.9, not folklore.",
   "fail": {
    "language": "python",
    "code": "resp = requests.get(url)                   # no timeout: can wait forever\nreply = stub.GetUser(request)              # gRPC sets no deadline by default"
   },
   "pass": {
    "language": "python",
    "code": "client = httpx.Client(timeout=httpx.Timeout(10.0, connect=2.0))   # set once on the client\nresp = client.get(url)\n\ndef GetProfile(self, request, context):\n    # bounded, and no longer than what the caller has left\n    return stub.GetUser(request, timeout=min(2.0, context.time_remaining()))"
   },
   "sources": [
    "https://docs.python.org/3/library/asyncio-task.html",
    "https://learn.microsoft.com/en-us/azure/architecture/patterns/retry",
    "https://grpc.io/docs/guides/deadlines/",
    "https://sre.google/sre-book/addressing-cascading-failures/",
    "https://builder.aws.com/content/3EumjoZascWd1oZiEgL8ORlv3qE/timeouts-retries-and-backoff-with-jitter",
    "https://learn.microsoft.com/en-us/aspnet/core/grpc/deadlines-cancellation"
   ],
   "orgs": [
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://docs.python.org/3/library/asyncio-task.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/architecture/patterns/retry",
      "https://learn.microsoft.com/en-us/aspnet/core/grpc/deadlines-cancellation"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://grpc.io/docs/guides/deadlines/",
      "https://sre.google/sre-book/addressing-cascading-failures/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://builder.aws.com/content/3EumjoZascWd1oZiEgL8ORlv3qE/timeouts-retries-and-backoff-with-jitter"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-any#svc-timeouts",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-tls-modern-only",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "security"
   ],
   "topics": [
    "tls",
    "network"
   ],
   "lang": "core",
   "applies_when": "The service or its load balancer terminates TLS.",
   "context": [
    "server"
   ],
   "tool_gate": "testssl.sh or a similar scan against the public endpoint in CI; the load balancer's named security policy checked in config review.",
   "note": "ASVS 12.1.1 and 12.1.2; IETF, OWASP and Microsoft back the Rule; NIST SP 800-52r2 still lets public-facing servers keep TLS 1.0 and 1.1 when necessary, so it is not counted; Microsoft advises deferring to the OS default over hardcoding a version; fn-tls-verify-on covers the client side",
   "lacks": null,
   "rule": "Accept only TLS 1.2 and TLS 1.3 on servers.",
   "why": "TLS 1.0 and 1.1 lack modern cipher suites and are deprecated by RFC 8996, and SSL 3.0 is broken outright. A server that still offers them lets an attacker on the path force a downgrade, and it fails compliance scans. Library defaults have moved to TLS 1.2, but an explicit lower setting, an old base image, or a load-balancer policy from years ago keeps the old versions alive. With TLS 1.2, prefer AEAD suites with forward secrecy.",
   "counterpoint": "\"Some of our users have old clients.\" Measure first: log the negotiated version, and if old clients are a real share, serve them from a separate endpoint with an end date instead of weakening every endpoint.",
   "fail": {
    "language": "python",
    "code": "ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)\nctx.minimum_version = ssl.TLSVersion.TLSv1"
   },
   "pass": {
    "language": "python",
    "code": "ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)\nctx.minimum_version = ssl.TLSVersion.TLSv1_2"
   },
   "sources": [
    "https://www.rfc-editor.org/rfc/rfc8996.html",
    "https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-52r2.pdf",
    "https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Security_Cheat_Sheet.html",
    "https://raw.githubusercontent.com/OWASP/ASVS/master/5.0/en/0x21-V12-Secure-Communication.md",
    "https://learn.microsoft.com/en-us/security/engineering/solving-tls1-problem"
   ],
   "orgs": [
    {
     "id": "ietf",
     "name": "IETF",
     "sources": [
      "https://www.rfc-editor.org/rfc/rfc8996.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nist",
     "name": "NIST",
     "sources": [
      "https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-52r2.pdf"
     ],
     "strength": "unjudged"
    },
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Security_Cheat_Sheet.html",
      "https://raw.githubusercontent.com/OWASP/ASVS/master/5.0/en/0x21-V12-Secure-Communication.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/security/engineering/solving-tls1-problem"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-tls-modern-only",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-update-rollback-on-failure",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "reliability"
   ],
   "topics": [
    "cli"
   ],
   "lang": "core",
   "applies_when": "The code replaces its own binary or an installed program with a new version.",
   "context": [],
   "tool_gate": "Test that makes the final step fail and checks the old version still runs.",
   "note": "Backed by Microsoft (Windows Installer automatic rollback), Google (Android A/B updates keep the old slot as a fallback) and Mender (the bootloader rolls back to the previous version); go-update documents the rename-and-restore steps for a single binary and is a reference",
   "lacks": null,
   "rule": "When an update fails, restore the previous version instead of leaving a missing or half-written one.",
   "why": "Deleting the old binary before the new one is in place leaves nothing to run if the copy fails, and the tool cannot update itself back. On Windows a running executable cannot be deleted but can be renamed, so move it aside first and restore it on failure.",
   "counterpoint": "\"Keeping a second copy costs disk space and clutter.\" One binary-sized file, removed after success, is cheap next to a tool that cannot start.",
   "fail": {
    "language": "go",
    "code": "exe, _ := os.Executable()\nos.Remove(exe) // fails on Windows for a running exe; if the next line fails, no binary is left\nreturn os.Rename(newBin, exe)"
   },
   "pass": {
    "language": "go",
    "code": "exe, err := os.Executable()\nif err != nil {\n\treturn err\n}\nold := exe + \".old\"\nif err := os.Rename(exe, old); err != nil {\n\treturn err\n}\nif err := os.Rename(newBin, exe); err != nil {\n\tif rerr := os.Rename(old, exe); rerr != nil {\n\t\treturn fmt.Errorf(\"update failed (%v) and rollback failed: %w\", err, rerr)\n\t}\n\treturn err\n}\nos.Remove(old) // best effort; retry at next start if the OS still holds it\nreturn nil"
   },
   "sources": [
    "https://learn.microsoft.com/en-us/windows/win32/msi/rollback-installation",
    "https://source.android.com/docs/core/ota/ab",
    "https://docs.mender.io/overview/introduction",
    "https://pkg.go.dev/github.com/inconshreveable/go-update"
   ],
   "orgs": [
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/windows/win32/msi/rollback-installation"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://source.android.com/docs/core/ota/ab"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mender",
     "name": "Mender",
     "sources": [
      "https://docs.mender.io/overview/introduction"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "go-update",
     "name": "go-update (Alan Shreve)",
     "sources": [
      "https://pkg.go.dev/github.com/inconshreveable/go-update"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-any#svc-update-rollback-on-failure",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-update-verify-provenance",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "security"
   ],
   "topics": [
    "supply-chain",
    "cli"
   ],
   "lang": "core",
   "applies_when": "The code downloads and installs or runs a new version of itself or of another program, such as a self-updater or an install script.",
   "context": [],
   "tool_gate": null,
   "note": "Backed by Sparkle (signed archive, keys kept off the web server), Sigstore (signing covers a compromised hosting site), TUF (SSL alone leaves one key to steal) and Debian (checksums are only trusted once the Release file is signed); GitHub and Google (Go checksum database) show the same practice; go-update is a reference",
   "lacks": null,
   "rule": "Verify a downloaded update against a signature, an attestation or a pinned key, or against a checksum that reached you by another route than the file; a checksum fetched from the same host as the file does not count.",
   "why": "An attacker who controls the download host replaces the archive and its checksum file together, so the check passes and the new binary runs with the user's rights. A signature made with a key that is not on that host, or a hash from a different channel, still fails for the attacker.",
   "counterpoint": "\"HTTPS already protects the download, and signing needs key management.\" TLS protects the connection to the host, not the files on it once the host or its account is breached. Keyless signing and build attestations remove most of the key handling.",
   "fail": {
    "language": "go",
    "code": "archive, _ := get(base + \"/tool.tar.gz\")\nwant, _ := get(base + \"/tool.tar.gz.sha256\") // same host as the archive\ngot := sha256.Sum256(archive)\nif hex.EncodeToString(got[:]) != strings.TrimSpace(string(want)) {\n\treturn errors.New(\"checksum mismatch\")\n}\ninstall(archive)"
   },
   "pass": {
    "language": "go",
    "code": "var releaseKey = mustDecodeKey(\"<base64 public key>\") // compiled into the binary, never downloaded\n\narchive, _ := get(base + \"/tool.tar.gz\")\nsig, _ := get(base + \"/tool.tar.gz.sig\")\nif !ed25519.Verify(releaseKey, archive, sig) {\n\treturn errors.New(\"bad signature\")\n}\ninstall(archive)"
   },
   "sources": [
    "https://sparkle-project.org/documentation/",
    "https://docs.sigstore.dev/about/overview/",
    "https://theupdateframework.io/security/",
    "https://wiki.debian.org/SecureApt",
    "https://docs.github.com/en/actions/concepts/security/artifact-attestations",
    "https://go.dev/blog/supply-chain",
    "https://pkg.go.dev/github.com/inconshreveable/go-update"
   ],
   "orgs": [
    {
     "id": "sparkle",
     "name": "Sparkle",
     "sources": [
      "https://sparkle-project.org/documentation/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "sigstore",
     "name": "Sigstore",
     "sources": [
      "https://docs.sigstore.dev/about/overview/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "tuf",
     "name": "The Update Framework",
     "sources": [
      "https://theupdateframework.io/security/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "debian",
     "name": "Debian",
     "sources": [
      "https://wiki.debian.org/SecureApt"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://docs.github.com/en/actions/concepts/security/artifact-attestations"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://go.dev/blog/supply-chain"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "go-update",
     "name": "go-update (Alan Shreve)",
     "sources": [
      "https://pkg.go.dev/github.com/inconshreveable/go-update"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-any#svc-update-verify-provenance",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-upload-validate-store",
   "level": "service",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "files",
    "uploads"
   ],
   "lang": "core",
   "applies_when": "The service accepts file uploads.",
   "context": [
    "server"
   ],
   "tool_gate": "Upload tests with a renamed `.php`, an oversized file and a zip bomb.",
   "note": null,
   "lacks": null,
   "rule": "Allowlist the file type, cap the size, and store each upload under a generated name where the server will not execute it.",
   "why": "The client-supplied name and Content-Type are attacker text. A user-chosen name allows path tricks, and an upload served from the web root with its original extension can run as code. Count the decompressed size of archives. Storing outside the web root and serving files through a handler, or from a separate domain, keeps a file that slips past the type check from running. [[fn-confine-file-path]] covers the path check.",
   "counterpoint": "\"The extension check is enough.\" The extension is attacker text, and double extensions or parser quirks get past a check on it. The generated name and a storage place that never executes files are what make a file that slips through harmless.",
   "fail": {
    "language": "python",
    "code": "path = f\"/var/www/uploads/{file.filename}\"\nfile.save(path)                       # any type, any size, public URL"
   },
   "pass": {
    "language": "python",
    "code": "if sniff_type(file.stream) not in {\"image/png\", \"image/jpeg\"} or size > 5_000_000: abort(400)   # type from the content, not the name\nfile.save(STORE / f\"{uuid4()}.bin\")   # outside web root; served by /files/<id>"
   },
   "sources": [
    "https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html",
    "https://api-security.owasp.org/editions/2023/en/0xa4-unrestricted-resource-consumption",
    "https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads",
    "https://portswigger.net/web-security/file-upload",
    "https://docs.djangoproject.com/en/5.2/topics/security/#user-uploaded-content"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html",
      "https://api-security.owasp.org/editions/2023/en/0xa4-unrestricted-resource-consumption"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads"
     ],
     "strength": "unjudged"
    },
    {
     "id": "portswigger",
     "name": "PortSwigger",
     "sources": [
      "https://portswigger.net/web-security/file-upload"
     ],
     "strength": "unjudged"
    },
    {
     "id": "django",
     "name": "Django Software Foundation",
     "sources": [
      "https://docs.djangoproject.com/en/5.2/topics/security/#user-uploaded-content"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-upload-validate-store",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "svc-webhook-replay-window",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "security"
   ],
   "topics": [
    "messaging",
    "webhooks"
   ],
   "lang": "core",
   "applies_when": "The code accepts signed webhooks or signed messages from another system.",
   "context": [
    "server"
   ],
   "tool_gate": "A test that replays a captured request with the timestamp moved outside the tolerance and asserts a 4xx, and one that changes only the timestamp header and asserts a 4xx.",
   "note": "tolerance on a signed creation time backed by Svix, Slack and Stripe (all three put the timestamp inside the signed content); IETF RFC 9421 permits the check and is a reference only",
   "lacks": null,
   "rule": "Reject an inbound signed message whose creation time is outside a short tolerance, and sign that time with the body so it cannot be swapped.",
   "why": "A valid signature proves who sent the message, not when. A captured request keeps its signature and can be sent again. Put the timestamp inside the signed content, as Slack, Svix and Stripe do. A timestamp in a separate header that the signature does not cover can be replaced by the attacker. Compare the timestamp with local time before doing any work. Retries are normal, so also store the message id and skip an id already processed. Deduplication stops a replay inside the window. The window stops one outside it and bounds how long the id store must keep entries.",
   "counterpoint": "\"A tight window drops delayed deliveries and retries.\" It does. RFC 9421 lets an application enforce a maximum signature age and leaves the number to it, and five minutes is the default of Slack and Stripe, not a standard. Pick a tolerance from the provider's retry schedule and your clock skew, log every reject with the offset, and keep deduplication by event id so a replay inside the window still does nothing. A signature with no timestamp at all cannot be bounded this way. The receiving endpoint has to rely on deduplication and on the sender's short-lived credential.",
   "fail": {
    "language": "python",
    "code": "if not verify_hmac(body, headers[\"X-Signature\"], secret):\n    return 401\napply_event(body)   # a captured request replays for as long as the secret lives"
   },
   "pass": {
    "language": "python",
    "code": "ts = headers[\"X-Timestamp\"]\nif abs(time.time() - int(ts)) > 300:\n    return 401\nif not verify_hmac(f\"{ts}.\".encode() + body, headers[\"X-Signature\"], secret):\n    return 401\napply_event(body)"
   },
   "sources": [
    "https://docs.svix.com/receiving/verifying-payloads/how-manual",
    "https://docs.slack.dev/authentication/verifying-requests-from-slack/",
    "https://www.rfc-editor.org/rfc/rfc9421.html",
    "https://docs.stripe.com/webhooks"
   ],
   "orgs": [
    {
     "id": "svix",
     "name": "Svix",
     "sources": [
      "https://docs.svix.com/receiving/verifying-payloads/how-manual"
     ],
     "strength": "unjudged"
    },
    {
     "id": "slack",
     "name": "Slack",
     "sources": [
      "https://docs.slack.dev/authentication/verifying-requests-from-slack/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "ietf",
     "name": "IETF",
     "sources": [
      "https://www.rfc-editor.org/rfc/rfc9421.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "stripe",
     "name": "Stripe",
     "sources": [
      "https://docs.stripe.com/webhooks"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/service-server#svc-webhook-replay-window",
   "markdown_url": "https://goodcodereview.com/service.md"
  },
  {
   "id": "sys-agent-tool-least-privilege",
   "level": "system",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security",
    "process"
   ],
   "topics": [
    "llm",
    "agents"
   ],
   "lang": "core",
   "applies_when": "An LLM agent can call tools, run code, or act on external systems.",
   "context": [],
   "tool_gate": "Tool registry reviewed in code; downstream credentials scoped in IAM and tested as the agent identity.",
   "note": "The narrow tool set is backed by OWASP, NCSC and Microsoft. This Microsoft page is not the MSRC prompt-injection post already cited and states least privilege and least action for the tools, data and operations an agent may use",
   "lacks": null,
   "rule": "Give the agent only the narrow tools and the minimal credentials its task needs.",
   "why": "Hallucination or an injected instruction can drive any tool the model holds. An open-ended shell or URL fetch hands over everything the service account can do. Where you can, run each tool as the requesting user rather than a shared admin identity, and let the downstream system check that user's rights on every call. [[sys-prompt-injection]] covers validating model output and human approval; this card covers how much the agent can reach in the first place.",
   "counterpoint": "\"Narrow tools make the agent less useful.\" Add tools when a task needs them and remove those that no task uses. A tool the model never calls is risk without benefit.",
   "fail": {
    "language": "text",
    "code": "tools = [shell(), http_fetch(any_url), db(admin_role)]       # one shared service identity"
   },
   "pass": {
    "language": "text",
    "code": "tools = [read_ticket(id), add_comment(id, text)]             # narrow functions\ncreds = user_scoped_token(user, scopes=[\"tickets:rw\"])       # downstream checks the user"
   },
   "sources": [
    "https://genai.owasp.org/llmrisk/llm062025-excessive-agency/",
    "https://genai.owasp.org/llmrisk/llm052025-improper-output-handling/",
    "https://www.ncsc.gov.uk/blog-post/prompt-injection-is-not-sql-injection",
    "https://www.microsoft.com/en-us/msrc/blog/2025/07/how-microsoft-defends-against-indirect-prompt-injection-attacks",
    "https://learn.microsoft.com/en-us/security/zero-trust/sfi/manage-agentic-risk"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://genai.owasp.org/llmrisk/llm062025-excessive-agency/",
      "https://genai.owasp.org/llmrisk/llm052025-improper-output-handling/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "ncsc",
     "name": "NCSC",
     "sources": [
      "https://www.ncsc.gov.uk/blog-post/prompt-injection-is-not-sql-injection"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://www.microsoft.com/en-us/msrc/blog/2025/07/how-microsoft-defends-against-indirect-prompt-injection-attacks",
      "https://learn.microsoft.com/en-us/security/zero-trust/sfi/manage-agentic-risk"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/system#sys-agent-tool-least-privilege",
   "markdown_url": "https://goodcodereview.com/system.md"
  },
  {
   "id": "sys-cache-bounds-invalidation",
   "level": "system",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "performance"
   ],
   "topics": [
    "caching",
    "memory"
   ],
   "lang": "core",
   "applies_when": "The code adds an in-process or shared cache.",
   "context": [],
   "tool_gate": null,
   "note": "Expiry is backed by AWS, Microsoft, Google Cloud and Cloudflare. Google Cloud and Cloudflare are vendors and state their own product knobs, and Google Cloud partly restates Redis's maxmemory and TTL pair. Size is not claimed here: Redis is the only clean organisation, the Caffeine wiki is one maintainer, and Microsoft's page only gives the consequence of an unbounded cache. Invalidation has one org (IETF, HTTP only) and value versioning none, so both sit in Why",
   "lacks": null,
   "rule": "Bound every cache by size and give its entries an expiry.",
   "why": "An unbounded cache is a memory leak that shows up only under production traffic. Redis has no memory limit by default on 64-bit builds. Choose the TTL from the staleness you can tolerate, because an arbitrary one either serves stale data or does not help. Also version the values, since old and new code read each other's entries during a rolling deploy, and give the cache hit, miss, and origin-request metrics and a way to invalidate.",
   "counterpoint": "\"Stale data is a business risk, not a review issue.\" It is one when the staleness window was never chosen. The rule asks for a decision, not a number. A soft TTL (refresh in the background) under a hard TTL (must refetch) balances freshness against load.",
   "fail": {
    "language": "text",
    "code": "CACHE = {}                         # grows forever, never expires\nredis.conf: maxmemory 0            # unlimited, used as a cache"
   },
   "pass": {
    "language": "text",
    "code": "cache = cachetools.TTLCache(maxsize=10_000, ttl=60)\nredis.conf: maxmemory 2gb, maxmemory-policy allkeys-lru"
   },
   "sources": [
    "https://builder.aws.com/content/3K9TeXp6strMiJ4vhakpRD7BQWO/caching-challenges-and-strategies",
    "https://redis.io/docs/latest/develop/reference/eviction/",
    "https://github.com/ben-manes/caffeine/wiki/Eviction",
    "https://www.rfc-editor.org/rfc/rfc9111.html#name-invalidating-stored-response",
    "https://learn.microsoft.com/en-us/azure/architecture/best-practices/caching",
    "https://cloud.google.com/memorystore/docs/redis/memory-management-best-practices",
    "https://developers.cloudflare.com/cache/how-to/edge-browser-cache-ttl/"
   ],
   "orgs": [
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://builder.aws.com/content/3K9TeXp6strMiJ4vhakpRD7BQWO/caching-challenges-and-strategies"
     ],
     "strength": "unjudged"
    },
    {
     "id": "redis",
     "name": "Redis",
     "sources": [
      "https://redis.io/docs/latest/develop/reference/eviction/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "ben-manes",
     "name": "Caffeine project",
     "sources": [
      "https://github.com/ben-manes/caffeine/wiki/Eviction"
     ],
     "strength": "unjudged"
    },
    {
     "id": "ietf",
     "name": "IETF",
     "sources": [
      "https://www.rfc-editor.org/rfc/rfc9111.html#name-invalidating-stored-response"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/architecture/best-practices/caching"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://cloud.google.com/memorystore/docs/redis/memory-management-best-practices"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cloudflare",
     "name": "Cloudflare",
     "sources": [
      "https://developers.cloudflare.com/cache/how-to/edge-browser-cache-ttl/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/system#sys-cache-bounds-invalidation",
   "markdown_url": "https://goodcodereview.com/system.md"
  },
  {
   "id": "sys-cells-limit-blast-radius",
   "level": "system",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "reliability"
   ],
   "topics": [
    "isolation",
    "availability"
   ],
   "lang": "core",
   "applies_when": "Many tenants, regions, or product surfaces share one cluster, control plane, or data store.",
   "context": [],
   "tool_gate": null,
   "note": "Netflix (each Titus region is divided into independent cells that bound any one control plane), Slack (moved critical user-facing services to cells to limit the blast radius of site failures), Airbnb (moved its metrics store to multiple clusters to set up separate failure domains), Cloudflare (control-plane postmortem: test the blast radius of failures and minimise the services one failure hits); per-tenant caps are left to svc-rate-limit-quota",
   "lacks": null,
   "rule": "Split shared infrastructure into independent cells, each with its own control plane and storage, and route each tenant or request to one cell, so one failure stops only that cell.",
   "why": "With one global cluster, every failure is global. A bad config push, an overloaded control plane, or a corrupt index stops every customer at once. Netflix divides each region into independent cells, which also caps how large any one control plane can grow. Slack moved its critical user-facing services to cells to limit the blast radius of site failures. Airbnb moved its metrics store to several clusters to get separate failure domains.",
   "counterpoint": "\"Cells cost more and add a routing layer.\" They do, with spare capacity in every cell, and a small service with one team may not earn them. Ask what one failure takes down. If one cluster outage or one bad deploy stops every customer, and that matters at your scale, mark `FAIL`. Cloudflare's postmortem asks for exactly that test of the blast radius. A per-tenant quota inside one cluster stops a noisy tenant but not a failed control plane. See [[svc-rate-limit-quota]]. Cells help only if a change reaches them one at a time. See [[prod-canary-staged-rollout]].",
   "fail": {
    "language": "yaml",
    "code": "# one cluster and one control plane serve every tenant\nclusters:\n  - name: metrics-prod\n    tenants: all"
   },
   "pass": {
    "language": "yaml",
    "code": "# each cell has its own control plane and storage; no cross-cell call on the request path\nclusters:\n  - name: metrics-cell-1\n    tenants: [payments, search]\n  - name: metrics-cell-2\n    tenants: [checkout, ads]"
   },
   "sources": [
    "https://netflixtechblog.com/single-tenant-titus-vm-level-isolation-on-a-multi-tenant-container-platform-6d8b1909d163",
    "https://slack.engineering/slacks-migration-to-a-cellular-architecture/",
    "https://airbnb.tech/infrastructure/building-a-fault-tolerant-metrics-storage-system-at-airbnb/",
    "https://blog.cloudflare.com/post-mortem-on-cloudflare-control-plane-and-analytics-outage/",
    "https://docs.aws.amazon.com/wellarchitected/latest/reducing-scope-of-impact-with-cell-based-architecture/what-is-a-cell-based-architecture.html",
    "https://americanexpress.io/cell-based-architecture-for-resilient-payment-systems/",
    "https://learn.microsoft.com/en-us/azure/architecture/patterns/deployment-stamp"
   ],
   "orgs": [
    {
     "id": "netflix",
     "name": "Netflix",
     "sources": [
      "https://netflixtechblog.com/single-tenant-titus-vm-level-isolation-on-a-multi-tenant-container-platform-6d8b1909d163"
     ],
     "strength": "unjudged"
    },
    {
     "id": "slack",
     "name": "Slack",
     "sources": [
      "https://slack.engineering/slacks-migration-to-a-cellular-architecture/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "airbnb",
     "name": "Airbnb",
     "sources": [
      "https://airbnb.tech/infrastructure/building-a-fault-tolerant-metrics-storage-system-at-airbnb/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cloudflare",
     "name": "Cloudflare",
     "sources": [
      "https://blog.cloudflare.com/post-mortem-on-cloudflare-control-plane-and-analytics-outage/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://docs.aws.amazon.com/wellarchitected/latest/reducing-scope-of-impact-with-cell-based-architecture/what-is-a-cell-based-architecture.html"
     ],
     "strength": "supports"
    },
    {
     "id": "amex",
     "name": "American Express",
     "sources": [
      "https://americanexpress.io/cell-based-architecture-for-resilient-payment-systems/"
     ],
     "strength": "supports"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/architecture/patterns/deployment-stamp"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "strong",
   "url": "https://goodcodereview.com/system#sys-cells-limit-blast-radius",
   "markdown_url": "https://goodcodereview.com/system.md"
  },
  {
   "id": "sys-consistent-ui-terms",
   "level": "system",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "usability",
    "accessibility"
   ],
   "topics": [
    "frontend",
    "design-system"
   ],
   "lang": "core",
   "applies_when": "Several screens or apps show the same action, status, or concept to the same users.",
   "context": [
    "ui"
   ],
   "tool_gate": "none; list the labels and icons for one action across all screens.",
   "note": "One icon is backed by W3C, GSA (USWDS) and Atlassian; Atlassian asks for existing icons to keep meanings consistent, which is softer than one icon per action. One label is backed by W3C, NN/g and GOV.UK. This card is about words and controls a user sees. It does not cover names in code or API field names.",
   "lacks": null,
   "rule": "Use one label and one icon for the same action and the same concept across all screens.",
   "why": "When \"Remove\", \"Delete\", and \"Discard\" all mean the same thing, users stop trusting any of them and ask whether each one differs. Keep the control in the same place on each screen, too. A familiar control costs no thought. A different one costs a pause, and in a destructive flow, a mistake.",
   "counterpoint": "\"Each team knows its users best.\" Consistent does not mean uniform. Differ where the job differs, and keep the wording and look of the shared actions the same. Drive the shared parts from the design-system component, so they cannot drift.",
   "fail": {
    "language": "text",
    "code": "Orders page:    [Delete]       (trash icon, right of row)\nCustomers page: [Remove]       (x icon, left of row)\nInvoices page:  [Discard draft](no icon, menu)"
   },
   "pass": {
    "language": "text",
    "code": "Orders, Customers, Invoices: [Delete] with the trash icon, last action in the row menu.\nA different outcome, such as archive, gets a different label: [Archive]."
   },
   "sources": [
    "https://www.nngroup.com/articles/ten-usability-heuristics/",
    "https://www.cs.umd.edu/users/ben/goldenrules.html",
    "https://www.w3.org/WAI/WCAG22/Understanding/consistent-identification.html",
    "https://www.gov.uk/guidance/government-design-principles",
    "https://design-system.service.gov.uk/components/error-message/",
    "https://designsystem.digital.gov/components/icon/",
    "https://atlassian.design/foundations/iconography"
   ],
   "orgs": [
    {
     "id": "nngroup",
     "name": "Nielsen Norman Group",
     "sources": [
      "https://www.nngroup.com/articles/ten-usability-heuristics/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "w3c",
     "name": "W3C",
     "sources": [
      "https://www.w3.org/WAI/WCAG22/Understanding/consistent-identification.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "govuk",
     "name": "GOV.UK",
     "sources": [
      "https://www.gov.uk/guidance/government-design-principles",
      "https://design-system.service.gov.uk/components/error-message/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "uswds",
     "name": "USWDS",
     "sources": [
      "https://designsystem.digital.gov/components/icon/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "atlassian",
     "name": "Atlassian",
     "sources": [
      "https://atlassian.design/foundations/iconography"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "shneiderman",
     "name": "Ben Shneiderman",
     "sources": [
      "https://www.cs.umd.edu/users/ben/goldenrules.html"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/system#sys-consistent-ui-terms",
   "markdown_url": "https://goodcodereview.com/system.md"
  },
  {
   "id": "sys-fanout-leg-deadlines",
   "level": "system",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "performance",
    "reliability"
   ],
   "topics": [
    "latency",
    "resilience"
   ],
   "lang": "core",
   "applies_when": "A request fans out to several backends, shards, or replicas and combines the answers.",
   "context": [],
   "tool_gate": null,
   "note": "Elastic states a per-shard deadline and Envoy a per-try deadline; AWS states a timeout on any remote call, which is the leg rule applied to a dependency but is not written about fan-out, so it only partly states the clause. A cap on fan-out width has one org (Google) and is left out of the Rule",
   "lacks": null,
   "rule": "Give every leg of a fan-out its own deadline, and decide in code what happens when a leg misses it: a partial result, a hedged request, or an error.",
   "why": "Fan-out turns rare slowness into common slowness. If each of 100 backends is slow on 1 request in 100, about 63% of requests wait on at least one slow backend. Without a per-leg deadline the slowest leg sets the latency of every request.",
   "counterpoint": "\"Hedging doubles the load.\" Sent at once to every replica, it does. Send the hedge only after a wait past about the 95th percentile, cap the attempts, and cancel the twin, and the extra load stays near 5%. Hedge only idempotent reads. A partial result needs a product decision. The deadline does not.",
   "fail": {
    "language": "text",
    "code": "results = await gather(*[shard.search(q) for shard in shards])   # waits for the slowest shard"
   },
   "pass": {
    "language": "text",
    "code": "results = await asyncio.gather(\n    *[asyncio.wait_for(shard.search(q), 0.08) for shard in shards],\n    return_exceptions=True)\nhits = [r for r in results if not isinstance(r, Exception)]\nreturn merge(hits, partial=len(hits) < len(shards))"
   },
   "sources": [
    "https://www.barroso.org/publications/TheTailAtScale.pdf",
    "https://grpc.io/docs/guides/request-hedging/",
    "https://cassandra.apache.org/doc/latest/cassandra/developing/cql/ddl.html",
    "https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-search",
    "https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto",
    "https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://www.barroso.org/publications/TheTailAtScale.pdf",
      "https://grpc.io/docs/guides/request-hedging/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "apache",
     "name": "Apache Software Foundation",
     "sources": [
      "https://cassandra.apache.org/doc/latest/cassandra/developing/cql/ddl.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "elastic",
     "name": "Elastic",
     "sources": [
      "https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-search"
     ],
     "strength": "unjudged"
    },
    {
     "id": "envoy",
     "name": "Envoy",
     "sources": [
      "https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto"
     ],
     "strength": "unjudged"
    },
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/system#sys-fanout-leg-deadlines",
   "markdown_url": "https://goodcodereview.com/system.md"
  },
  {
   "id": "sys-health-liveness-readiness",
   "level": "system",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "reliability"
   ],
   "topics": [
    "health",
    "availability"
   ],
   "lang": "core",
   "applies_when": "A service exposes a health endpoint used by a load balancer, an orchestrator, or auto-replacement.",
   "context": [],
   "tool_gate": null,
   "note": null,
   "lacks": null,
   "rule": "Wire load balancers and restarters to liveness and local checks only, never to shared-dependency checks.",
   "why": "Dependency status belongs in alarms. If the database blips, every host fails its check, all are removed at once, and a partial outage becomes total.",
   "counterpoint": "Readiness that reflects a dependency feels more honest. AWS accepts fail-open designs but is wary of behavior it cannot fully test, so a fail-open health policy counts as PASS only if you have actually tested it.",
   "fail": {
    "language": "text",
    "code": "GET /health -> SELECT 1 on primary db     # drives LB removal and restart"
   },
   "pass": {
    "language": "text",
    "code": "/live: process responds ; /ready: local init done, no shared-dependency call\ndependency status -> metric + alarm only"
   },
   "sources": [
    "https://aws.amazon.com/builders-library/implementing-health-checks/",
    "https://learn.microsoft.com/en-us/azure/architecture/patterns/health-endpoint-monitoring",
    "https://docs.gitlab.com/administration/monitoring/health_check/"
   ],
   "orgs": [
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://aws.amazon.com/builders-library/implementing-health-checks/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/architecture/patterns/health-endpoint-monitoring"
     ],
     "strength": "supports"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://docs.gitlab.com/administration/monitoring/health_check/"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/system#sys-health-liveness-readiness",
   "markdown_url": "https://goodcodereview.com/system.md"
  },
  {
   "id": "sys-index-concurrently",
   "level": "system",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "compatibility"
   ],
   "topics": [
    "database",
    "migration",
    "postgres"
   ],
   "lang": "core",
   "applies_when": "A migration creates an index on an existing large or busy table.",
   "context": [
    "data"
   ],
   "tool_gate": "Migration linter.",
   "note": null,
   "lacks": null,
   "rule": "Create indexes with `CONCURRENTLY`, outside a transaction block, and handle a failed build that leaves an INVALID index.",
   "why": "A plain build blocks writes for its whole duration. A concurrent build fails inside a transaction. A concurrent build that fails leaves an invalid index that queries ignore but writes still pay for.",
   "counterpoint": "\"CONCURRENTLY is slower.\" It does two scans, waits for older transactions, and runs only one concurrent build per table at a time. That is the price of not blocking writes. For a unique index, others can see violations before a failed build is cleaned up.",
   "fail": {
    "language": "sql",
    "code": "CREATE INDEX idx_orders_user ON orders (user_id);"
   },
   "pass": {
    "language": "sql",
    "code": "-- migration with the DDL transaction disabled\nDROP INDEX CONCURRENTLY IF EXISTS idx_orders_user;\nCREATE INDEX CONCURRENTLY idx_orders_user ON orders (user_id);"
   },
   "sources": [
    "https://www.postgresql.org/docs/current/sql-createindex.html",
    "https://docs.gitlab.com/development/migration_style_guide/",
    "https://devcenter.heroku.com/articles/postgresql-indexes",
    "https://thoughtbot.com/blog/how-to-create-postgres-indexes-concurrently-in"
   ],
   "orgs": [
    {
     "id": "postgresql",
     "name": "PostgreSQL",
     "sources": [
      "https://www.postgresql.org/docs/current/sql-createindex.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://docs.gitlab.com/development/migration_style_guide/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "heroku",
     "name": "Heroku",
     "sources": [
      "https://devcenter.heroku.com/articles/postgresql-indexes"
     ],
     "strength": "supports"
    },
    {
     "id": "thoughtbot",
     "name": "thoughtbot",
     "sources": [
      "https://thoughtbot.com/blog/how-to-create-postgres-indexes-concurrently-in"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/system#sys-index-concurrently",
   "markdown_url": "https://goodcodereview.com/system.md"
  },
  {
   "id": "sys-load-shedding-bounded-queues",
   "level": "system",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "reliability"
   ],
   "topics": [
    "overload",
    "queues"
   ],
   "lang": "core",
   "applies_when": "Work is buffered in a queue, a channel, or a thread pool between components.",
   "context": [
    "server"
   ],
   "tool_gate": "Lint for unbounded queue constructors.",
   "note": null,
   "lacks": null,
   "rule": "Bound every queue and reject excess work cheaply.",
   "why": "An unbounded queue turns overload into minutes of latency, and by the time old items are served, callers have given up. Also keep a way to sideline old or excess messages (see PASS).",
   "counterpoint": "\"Dropping work loses data.\" A bounded queue only makes the loss explicit and early. For work that must not be lost, spill to durable storage rather than memory. Shedding itself must be cheap. Slowing fast errors on purpose can stop a load balancer from steering more traffic at a shedding host.",
   "fail": {
    "language": "text",
    "code": "q = Queue()                      # no maxsize\non overload: enqueue anyway; no age alarm, no spillover"
   },
   "pass": {
    "language": "text",
    "code": "q = Queue(maxsize=1000); if full: return 503 fast\nalarm on age of oldest item; spillover queue for old deltas"
   },
   "sources": [
    "https://aws.amazon.com/builders-library/avoiding-insurmountable-queue-backlogs/",
    "https://aws.amazon.com/builders-library/fairness-in-multi-tenant-systems/",
    "https://sre.google/sre-book/addressing-cascading-failures/",
    "https://sre.google/sre-book/handling-overload/",
    "https://github.com/Netflix/concurrency-limits",
    "https://builder.aws.com/content/3Eun1EEyX6p2e3VYNyRLSJzLuMV/using-load-shedding-to-avoid-overload"
   ],
   "orgs": [
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://aws.amazon.com/builders-library/avoiding-insurmountable-queue-backlogs/",
      "https://aws.amazon.com/builders-library/fairness-in-multi-tenant-systems/",
      "https://builder.aws.com/content/3Eun1EEyX6p2e3VYNyRLSJzLuMV/using-load-shedding-to-avoid-overload"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://sre.google/sre-book/addressing-cascading-failures/",
      "https://sre.google/sre-book/handling-overload/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "netflix",
     "name": "Netflix",
     "sources": [
      "https://github.com/Netflix/concurrency-limits"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/system#sys-load-shedding-bounded-queues",
   "markdown_url": "https://goodcodereview.com/system.md"
  },
  {
   "id": "sys-lock-fencing",
   "level": "system",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "reliability",
    "correctness"
   ],
   "topics": [
    "locks",
    "distributed"
   ],
   "lang": "core",
   "applies_when": "A TTL lock or lease guards a write to an external store.",
   "context": [],
   "tool_gate": null,
   "note": "Apache Hadoop and Hazelcast state the rule; Hazelcast is a vendor and its page draws on Chubby and Kleppmann. Microsoft and AWS, both vendors, state the compare-and-set arm only. Google's Chubby paper (OSDI 2006, section 2.4) says the recipient server tests whether the sequencer is still valid. The AWS Builders Library leader-election page backs the Why only; it names the hazard and never says to check at the resource",
   "lacks": null,
   "rule": "Check a correctness lock on the resource side (fencing token or compare-and-set), never by lock possession alone.",
   "why": "A holder can pause past its TTL, lose the lock and still write. Only the store can reject the stale writer.",
   "counterpoint": "\"Our lock library is safe, so holding the lock is enough.\" Whether a given algorithm such as Redlock is safe is disputed, but that a paused holder can still write is not. Locks used only for efficiency (avoiding duplicate work) may be loose. For correctness, state that it is a correctness lock and make the store reject stale writers, with a monotonically increasing token or a unique token checked by compare-and-set, whatever the lock implementation.",
   "fail": {
    "language": "text",
    "code": "if redis.set(key, id, nx=True, px=10000):\n    db.update(row)                 # pause here => two writers"
   },
   "pass": {
    "language": "text",
    "code": "token = lock.acquire()             # monotonically increasing\ndb.update(row, where fence < token, set fence = token)"
   },
   "sources": [
    "https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html",
    "https://antirez.com/news/101",
    "https://aws.amazon.com/builders-library/leader-election-in-distributed-systems/",
    "https://www.usenix.org/legacy/event/osdi06/tech/full_papers/burrows/burrows.pdf",
    "https://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-hdfs/HDFSHighAvailabilityWithQJM.html",
    "https://docs.hazelcast.com/hazelcast/5.7/data-structures/fencedlock",
    "https://learn.microsoft.com/en-us/azure/storage/blobs/concurrency-manage",
    "https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithItems.html"
   ],
   "orgs": [
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://aws.amazon.com/builders-library/leader-election-in-distributed-systems/",
      "https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithItems.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://www.usenix.org/legacy/event/osdi06/tech/full_papers/burrows/burrows.pdf"
     ],
     "strength": "unjudged"
    },
    {
     "id": "apache",
     "name": "Apache Software Foundation",
     "sources": [
      "https://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-hdfs/HDFSHighAvailabilityWithQJM.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "hazelcast",
     "name": "Hazelcast",
     "sources": [
      "https://docs.hazelcast.com/hazelcast/5.7/data-structures/fencedlock"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/storage/blobs/concurrency-manage"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "kleppmann",
     "name": "Martin Kleppmann",
     "sources": [
      "https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "salvatore",
     "name": "Salvatore Sanfilippo",
     "sources": [
      "https://antirez.com/news/101"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/system#sys-lock-fencing",
   "markdown_url": "https://goodcodereview.com/system.md"
  },
  {
   "id": "sys-migration-expand-contract",
   "level": "system",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "reliability",
    "compatibility"
   ],
   "topics": [
    "database",
    "migration",
    "deploys"
   ],
   "lang": "core",
   "applies_when": "A change drops, renames, or retypes a column, or mixes schema change with a data backfill.",
   "context": [
    "data"
   ],
   "tool_gate": "Migration linter for DROP/RENAME COLUMN.",
   "note": "3 orgs per clause. Batching rests on GitLab, Ecto, and CockroachDB (a database vendor). The separate migration rests on GitLab, Ecto, and Stripe, which shows it by structure and never says batches",
   "lacks": null,
   "rule": "Run data backfills in batches, in a migration separate from the schema change.",
   "why": "A large backfill in one transaction holds locks for its whole run, and a failure halfway rolls all of it back. Batches with a pause between them keep each lock short and let the backfill resume. [[chg-expand-contract]] covers the order of releases around it.",
   "counterpoint": "\"One `UPDATE` is simpler than a batched job.\" On a small table it is, and this card does not apply there. Once the update holds a lock long enough to block live traffic, batch it.",
   "fail": {
    "language": "text",
    "code": "release N: RENAME COLUMN a TO b ; UPDATE big_table SET b = ... (one tx)"
   },
   "pass": {
    "language": "text",
    "code": "N: add b, dual-write, ignore a in code ; N+1: batched backfill ; N+2: drop a"
   },
   "sources": [
    "https://docs.gitlab.com/development/database/avoiding_downtime_in_migrations/",
    "https://docs.gitlab.com/development/migration_style_guide/",
    "https://stripe.com/blog/online-migrations",
    "https://github.com/ankane/strong_migrations",
    "https://ecto-sql.hexdocs.pm/backfilling_data.html",
    "https://docs.cockroachlabs.com/docs/stable/bulk-update-data"
   ],
   "orgs": [
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://docs.gitlab.com/development/database/avoiding_downtime_in_migrations/",
      "https://docs.gitlab.com/development/migration_style_guide/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "stripe",
     "name": "Stripe",
     "sources": [
      "https://stripe.com/blog/online-migrations"
     ],
     "strength": "unjudged"
    },
    {
     "id": "ecto",
     "name": "Ecto",
     "sources": [
      "https://ecto-sql.hexdocs.pm/backfilling_data.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cockroach",
     "name": "CockroachDB",
     "sources": [
      "https://docs.cockroachlabs.com/docs/stable/bulk-update-data"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "kane",
     "name": "Andrew Kane",
     "sources": [
      "https://github.com/ankane/strong_migrations"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/system#sys-migration-expand-contract",
   "markdown_url": "https://goodcodereview.com/system.md"
  },
  {
   "id": "sys-migration-lock-timeout",
   "level": "system",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "compatibility"
   ],
   "topics": [
    "database",
    "migration",
    "postgres"
   ],
   "lang": "core",
   "applies_when": "A migration runs ALTER TABLE on a busy PostgreSQL table.",
   "context": [
    "data"
   ],
   "tool_gate": "Migration linter (e.g. flag ALTER without lock_timeout).",
   "note": "lock-queue consequence is inference, not stated in the Postgres page read",
   "lacks": null,
   "rule": "Set `lock_timeout` for the migration session and retry on timeout.",
   "why": "The default is 0, meaning wait forever, and most ALTER forms need the strongest lock. A migration stuck waiting can make later queries on that table wait behind it (inferred from how lock queues work).",
   "counterpoint": "\"Set it globally.\" The Postgres docs advise against setting it in postgresql.conf because it affects every session, so scope it to the migration. Also, with a non-zero `statement_timeout`, a larger `lock_timeout` has no effect.",
   "fail": {
    "language": "sql",
    "code": "ALTER TABLE orders ADD COLUMN note text;   -- no lock_timeout"
   },
   "pass": {
    "language": "sql",
    "code": "SET lock_timeout = '5s';\nALTER TABLE orders ADD COLUMN note text;   -- retry on timeout"
   },
   "sources": [
    "https://www.postgresql.org/docs/current/runtime-config-client.html",
    "https://www.postgresql.org/docs/current/sql-altertable.html",
    "https://docs.gitlab.com/development/migration_style_guide/",
    "https://gocardless.com/blog/zero-downtime-postgres-migrations-a-little-help",
    "https://postgres.ai/blog/20210923-zero-downtime-postgres-schema-migrations-lock-timeout-and-retries",
    "https://github.com/doctolib/safe-pg-migrations"
   ],
   "orgs": [
    {
     "id": "postgresql",
     "name": "PostgreSQL",
     "sources": [
      "https://www.postgresql.org/docs/current/runtime-config-client.html",
      "https://www.postgresql.org/docs/current/sql-altertable.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://docs.gitlab.com/development/migration_style_guide/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gocardless",
     "name": "GoCardless",
     "sources": [
      "https://gocardless.com/blog/zero-downtime-postgres-migrations-a-little-help"
     ],
     "strength": "supports"
    },
    {
     "id": "postgresai",
     "name": "Postgres.ai",
     "sources": [
      "https://postgres.ai/blog/20210923-zero-downtime-postgres-schema-migrations-lock-timeout-and-retries"
     ],
     "strength": "supports"
    },
    {
     "id": "doctolib",
     "name": "Doctolib",
     "sources": [
      "https://github.com/doctolib/safe-pg-migrations"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "strong",
   "url": "https://goodcodereview.com/system#sys-migration-lock-timeout",
   "markdown_url": "https://goodcodereview.com/system.md"
  },
  {
   "id": "sys-n-plus-one-pooling",
   "level": "system",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "performance"
   ],
   "topics": [
    "database"
   ],
   "lang": "core",
   "applies_when": "Code loads related rows in a loop, or opens database connections per request.",
   "context": [
    "data"
   ],
   "tool_gate": "Query-count assertion in tests; ORM N+1 detector.",
   "note": "N+1 half has 4 orgs (Django, Rails, SQLAlchemy, Microsoft); the bounded pool rests on 3 partial sources; the index clause has no source beyond topic level",
   "lacks": null,
   "rule": "Fetch related rows in one query or batch, never one query per row.",
   "why": "A query per row multiplies latency by result size. Also back filter and join columns with an index and use a bounded connection pool: an unindexed filter turns each query into a scan, and a connection per request exhausts the database under load.",
   "counterpoint": "\"Measure first.\" Yes, for micro-optimizations, but a query inside a loop over unbounded input is a defect by shape.",
   "fail": {
    "language": "python",
    "code": "for o in Order.objects.all():\n    print(o.user.email)                              # one query per order"
   },
   "pass": {
    "language": "python",
    "code": "for o in Order.objects.select_related(\"user\"):\n    print(o.user.email)                              # one query with a join"
   },
   "sources": [
    "https://docs.djangoproject.com/en/stable/ref/models/querysets/",
    "https://guides.rubyonrails.org/active_record_querying.html",
    "https://docs.sqlalchemy.org/en/20/orm/queryguide/relationships.html",
    "https://learn.microsoft.com/en-us/azure/architecture/antipatterns/chatty-io/",
    "https://nodejs.org/api/http.html#class-httpagent",
    "https://www.pgbouncer.org/features.html",
    "https://www.postgresql.org/docs/current/runtime-config-connection.html"
   ],
   "orgs": [
    {
     "id": "django",
     "name": "Django Software Foundation",
     "sources": [
      "https://docs.djangoproject.com/en/stable/ref/models/querysets/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "rails",
     "name": "Ruby on Rails",
     "sources": [
      "https://guides.rubyonrails.org/active_record_querying.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "sqlalchemy",
     "name": "SQLAlchemy",
     "sources": [
      "https://docs.sqlalchemy.org/en/20/orm/queryguide/relationships.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/architecture/antipatterns/chatty-io/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "nodejs",
     "name": "OpenJS Foundation",
     "sources": [
      "https://nodejs.org/api/http.html#class-httpagent"
     ],
     "strength": "unjudged"
    },
    {
     "id": "pgbouncer",
     "name": "PgBouncer project",
     "sources": [
      "https://www.pgbouncer.org/features.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "postgresql",
     "name": "PostgreSQL",
     "sources": [
      "https://www.postgresql.org/docs/current/runtime-config-connection.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/system#sys-n-plus-one-pooling",
   "markdown_url": "https://goodcodereview.com/system.md"
  },
  {
   "id": "sys-pii-minimise-retention",
   "level": "system",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "security"
   ],
   "topics": [
    "privacy",
    "data"
   ],
   "lang": "core",
   "applies_when": "The system stores, copies or logs personal or otherwise sensitive data.",
   "context": [],
   "tool_gate": "Retention jobs with a TTL on tables and buckets; a data inventory checked in CI.",
   "note": "The retention period is backed by ICO and Microsoft; GDPR Art. 5 is the only other statement and it is law, not an organisation. Delete or anonymise is backed by ICO, OWASP and Microsoft. Microsoft is a vendor and its page covers Microsoft 365 content, not application tables or logs. The legal retention periods differ by jurisdiction; the source gives only the engineering principle",
   "lacks": null,
   "rule": "Give every store of personal or sensitive data a retention period, and delete or anonymise the data when it ends.",
   "why": "Data you do not keep cannot leak. Keep only the fields the feature needs, too. Full card numbers, national IDs and raw request bodies pile up in logs, analytics tables and backups, and nobody owns their deletion. Tokenise or truncate when the full value is not needed, and encrypt what you keep. This is a technical check, not a legal opinion.",
   "counterpoint": "\"We might need it later.\" A guess is not a purpose. Keep it when a named feature or legal duty needs it, and write that reason next to the retention rule.",
   "fail": {
    "language": "text",
    "code": "analytics.events: full email, DOB and phone, no TTL\nlogs: raw request body with the national ID field, kept forever"
   },
   "pass": {
    "language": "text",
    "code": "analytics.events: hashed user id, TTL 90 days\nlogs: id field masked; table has a retention policy and an owner"
   },
   "sources": [
    "https://top10.owasp.org/2025/A04_2025-Cryptographic_Failures",
    "https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html",
    "https://ico.org.uk/for-organisations/uk-gdpr-guidance-and-resources/data-protection-principles/a-guide-to-the-data-protection-principles/storage-limitation/",
    "https://gdpr-info.eu/art-5-gdpr/",
    "https://learn.microsoft.com/en-us/purview/retention"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://top10.owasp.org/2025/A04_2025-Cryptographic_Failures",
      "https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "ico",
     "name": "ICO",
     "sources": [
      "https://ico.org.uk/for-organisations/uk-gdpr-guidance-and-resources/data-protection-principles/a-guide-to-the-data-protection-principles/storage-limitation/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gdpr-info",
     "name": "intersoft consulting",
     "sources": [
      "https://gdpr-info.eu/art-5-gdpr/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/purview/retention"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/system#sys-pii-minimise-retention",
   "markdown_url": "https://goodcodereview.com/system.md"
  },
  {
   "id": "sys-prompt-injection",
   "level": "system",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "llm"
   ],
   "lang": "core",
   "applies_when": "An LLM reads untrusted text (user input, web pages, documents) and can call tools or produce output that code acts on.",
   "context": [],
   "tool_gate": null,
   "note": null,
   "lacks": null,
   "rule": "Validate model output deterministically before code acts on it.",
   "why": "Injected instructions in retrieved text can steer tool calls. No known prevention is fool-proof, and RAG or fine-tuning do not fully fix it, so the goal is to limit what a fooled model can make the code do. Also keep credentials in code, not in the model, and require human approval for high-risk actions.",
   "counterpoint": "\"Guardrail prompts block it.\" They reduce risk but are not a boundary. The permission model has to hold even when the model is fooled.",
   "fail": {
    "language": "text",
    "code": "tool_call = llm(user_doc); shell(tool_call.cmd)     # model holds the API token"
   },
   "pass": {
    "language": "text",
    "code": "cmd = parse_strict(llm(...)); if cmd not in ALLOWED: reject\ndestructive actions -> human confirm ; untrusted text labelled in the prompt"
   },
   "sources": [
    "https://genai.owasp.org/llmrisk/llm01-prompt-injection/",
    "https://www.ncsc.gov.uk/blog-post/prompt-injection-is-not-sql-injection",
    "https://www.microsoft.com/en-us/msrc/blog/2025/07/how-microsoft-defends-against-indirect-prompt-injection-attacks",
    "https://blog.google/security/mitigating-prompt-injection-attacks/"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://genai.owasp.org/llmrisk/llm01-prompt-injection/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "ncsc",
     "name": "NCSC",
     "sources": [
      "https://www.ncsc.gov.uk/blog-post/prompt-injection-is-not-sql-injection"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://www.microsoft.com/en-us/msrc/blog/2025/07/how-microsoft-defends-against-indirect-prompt-injection-attacks"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://blog.google/security/mitigating-prompt-injection-attacks/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/system#sys-prompt-injection",
   "markdown_url": "https://goodcodereview.com/system.md"
  },
  {
   "id": "sys-secrets-and-errors",
   "level": "system",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security",
    "correctness"
   ],
   "topics": [
    "secrets",
    "errors"
   ],
   "lang": "core",
   "applies_when": "The code handles credentials, logs data, or returns errors to clients.",
   "context": [],
   "tool_gate": "Secret scanner in CI; log-redaction tests.",
   "note": "Fail closed is backed by OWASP, MITRE (CWE-636 and CWE-209) and Microsoft, whose page states it only for certificate-validation errors. RFC 9457 backs the no-internals half. Secrets in source rest on 12factor and OWASP only, so they stay in the Why",
   "lacks": null,
   "rule": "Fail closed: a validation or auth error must deny, never continue, and an error response must not carry internals such as stack traces, SQL or tokens.",
   "why": "A catch-and-continue around a validation step grants access when the check crashes. Stack traces and SQL in a response show an attacker how the system is built, and a token there can be reused. Keep secrets out of source and logs too: read them from the environment or a secrets manager.",
   "counterpoint": "\"A broad `except Exception` breaks the specific-exceptions rule.\" At a security boundary, fail-closed wins: catch broadly, log redacted, deny, and never use a bare `except:`. \"We need detail to debug.\" Log it server-side with a correlation id.",
   "fail": {
    "language": "text",
    "code": "return {\"error\": traceback.format_exc()}\ntry: check_token(t)\nexcept Exception: pass            # continues as authorized"
   },
   "pass": {
    "language": "text",
    "code": "return {\"error\": \"internal\", \"id\": err_id}   # details to logs, redacted\ntry: check_token(t)\nexcept Exception as err:\n    log.warning(\"auth failed: %s\", redact(err)); deny()"
   },
   "sources": [
    "https://raw.githubusercontent.com/OWASP/ASVS/master/5.0/en/0x25-V16-Security-Logging-and-Error-Handling.md",
    "https://owasp.org/Top10/2025/",
    "https://cwe.mitre.org/data/definitions/636.html",
    "https://cwe.mitre.org/data/definitions/209.html",
    "https://12factor.net/config",
    "https://web.mit.edu/Saltzer/www/publications/protection/Basic.html",
    "https://www.rfc-editor.org/rfc/rfc9457.html",
    "https://learn.microsoft.com/en-us/azure/developer/azure-mcp-server/security"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://raw.githubusercontent.com/OWASP/ASVS/master/5.0/en/0x25-V16-Security-Logging-and-Error-Handling.md",
      "https://owasp.org/Top10/2025/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mitre",
     "name": "MITRE",
     "sources": [
      "https://cwe.mitre.org/data/definitions/636.html",
      "https://cwe.mitre.org/data/definitions/209.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "heroku",
     "name": "Heroku",
     "sources": [
      "https://12factor.net/config"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mit",
     "name": "MIT",
     "sources": [
      "https://web.mit.edu/Saltzer/www/publications/protection/Basic.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "ietf",
     "name": "IETF",
     "sources": [
      "https://www.rfc-editor.org/rfc/rfc9457.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/azure/developer/azure-mcp-server/security"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/system#sys-secrets-and-errors",
   "markdown_url": "https://goodcodereview.com/system.md"
  },
  {
   "id": "sys-ssrf",
   "level": "system",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "security"
   ],
   "topics": [
    "ssrf"
   ],
   "lang": "core",
   "applies_when": "The server fetches a URL that a user or external data supplies.",
   "context": [],
   "tool_gate": "CodeQL `py/full-ssrf`; an egress proxy or firewall allowlist.",
   "note": null,
   "lacks": null,
   "rule": "Fetch a user-supplied URL only when its host matches a server-side allowlist.",
   "why": "The server can reach networks that the attacker cannot, such as internal services and the cloud metadata address, and an open fetch lets the attacker read them. A hostname allowlist alone does not stop DNS rebinding, so also check that the resolved address is not private and connect to that address. Turn off redirect following, or check each redirect target the same way.",
   "counterpoint": "\"Blocklisting private ranges is enough.\" Alternate IP forms, URL parser tricks and open redirects get past a blocklist. An allowlist fails closed when someone finds a new trick.",
   "fail": {
    "language": "python",
    "code": "requests.get(request.args[\"url\"])"
   },
   "pass": {
    "language": "python",
    "code": "u = urlparse(request.args[\"url\"])\nif u.scheme != \"https\" or u.hostname not in ALLOWED_HOSTS: abort(400)\nrequests.get(u.geturl(), allow_redirects=False, timeout=5)"
   },
   "sources": [
    "https://owasp.org/Top10/2025/",
    "https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html",
    "https://codeql.github.com/codeql-query-help/python/py-full-ssrf/",
    "https://docs.aws.amazon.com/codeguru/detector-library/go/server-side-request-forgery/",
    "https://portswigger.net/web-security/ssrf"
   ],
   "orgs": [
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://owasp.org/Top10/2025/",
      "https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://codeql.github.com/codeql-query-help/python/py-full-ssrf/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://docs.aws.amazon.com/codeguru/detector-library/go/server-side-request-forgery/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "portswigger",
     "name": "PortSwigger",
     "sources": [
      "https://portswigger.net/web-security/ssrf"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/system#sys-ssrf",
   "markdown_url": "https://goodcodereview.com/system.md"
  },
  {
   "id": "chg-agent-grounded-code",
   "level": "change",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "maintainability",
    "process"
   ],
   "topics": [
    "agents",
    "ai-generated",
    "reuse"
   ],
   "lang": "core",
   "applies_when": "The change was written fully or partly by an AI agent.",
   "context": [],
   "tool_gate": "Compiler, type checker and dependency-resolution in CI catch non-existent imports and calls.",
   "note": null,
   "lacks": null,
   "rule": "Verify that every import and external call the agent wrote exists with that signature.",
   "why": "Agents invent plausible APIs and rewrite nearby utilities without searching. A study of 16 code models found that at least about 5% of the packages they suggested (about 22% for open-source models) did not exist, and an attacker can publish a malicious package under a name that models keep inventing. A green build proves only what the compiler sees. [[chg-reuse-before-write]] covers re-implemented helpers. The author must also be able to explain each hunk; see [[chg-agent-independent-review]].",
   "counterpoint": "\"Humans make the same mistakes.\" True, but agents make them fluently and at volume, and disclosure alone does not catch them. Kubernetes requires disclosure and an author who can explain the change. A compiler and a dependency check catch the mechanical part, so use this card for calls that resolve but behave differently than assumed.",
   "fail": {
    "language": "python",
    "code": "import requests_cache_pro                # package does not exist, or exists only because an attacker registered it\ntotal = sum_cents(items)                 # new helper; money.sum_cents() already exists\nresp = client.fetch_json(url, retries=3) # fetch_json has no retries param"
   },
   "pass": {
    "language": "python",
    "code": "from money import sum_cents\nresp = client.fetch_json(url)   # signature checked against source"
   },
   "sources": [
    "https://www.kubernetes.dev/docs/guide/pull-requests/",
    "https://google.github.io/eng-practices/review/reviewer/looking-for.html",
    "https://arxiv.org/abs/2406.10279",
    "https://labs.cloudsecurityalliance.org/research/csa-research-note-ai-codegen-vulnerability-debt-20260406-csa/"
   ],
   "orgs": [
    {
     "id": "kubernetes",
     "name": "Kubernetes",
     "sources": [
      "https://www.kubernetes.dev/docs/guide/pull-requests/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://google.github.io/eng-practices/review/reviewer/looking-for.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "utsa",
     "name": "University of Texas at San Antonio",
     "sources": [
      "https://arxiv.org/abs/2406.10279"
     ],
     "strength": "unjudged"
    },
    {
     "id": "csa",
     "name": "Cloud Security Alliance",
     "sources": [
      "https://labs.cloudsecurityalliance.org/research/csa-research-note-ai-codegen-vulnerability-debt-20260406-csa/"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/change#chg-agent-grounded-code",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-agent-independent-review",
   "level": "change",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "process"
   ],
   "topics": [
    "agents",
    "review"
   ],
   "lang": "core",
   "applies_when": "An agent wrote the change, and an agent or the same session is asked to review it.",
   "context": [],
   "tool_gate": null,
   "note": "a person's sign-off on agent-written changes has Stripe and Dropbox (firm) and Uber (maintenance tasks only); Kubernetes covers AI-assisted PRs with a human author; Spotify auto-merges some agent PRs; nobody backs a fresh agent as the sign-off (Cloudflare's separate-session AI reviewers review engineer-written code)",
   "lacks": null,
   "rule": "Get sign-off from a person or a fresh-context reviewer who has the spec but not the author's reasoning; self-review by the writing session does not count.",
   "why": "The same context repeats the same blind spots and assumptions. An approval without real reading is a rubber stamp. The person who submits the change must also be able to explain each hunk.",
   "counterpoint": "A same-session pass still catches typos and leftovers. Keep it as the author's self-review step, not as the gate. Meta tracks \"eyeball time\" as a guardrail against rubber-stamping, the failure this card targets.",
   "fail": {
    "language": "text",
    "code": "Session writes the diff, then \"reviews\" it: \"no issues found\". Merged."
   },
   "pass": {
    "language": "text",
    "code": "Fresh reviewer given spec + diff + test output, runs the branch, files findings.\nA human who can explain the change approves."
   },
   "sources": [
    "https://www.kubernetes.dev/docs/guide/pull-requests/",
    "https://engineering.fb.com/2022/11/16/culture/meta-code-review-time-improving/",
    "https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents",
    "https://www.uber.com/blog/efficient-software-factory/",
    "https://dropbox.tech/culture/beyond-code-generation-rethinking-engineering-productivity-in-the-age-of-ai-agents",
    "https://www.anthropic.com/engineering/claude-code-best-practices"
   ],
   "orgs": [
    {
     "id": "kubernetes",
     "name": "Kubernetes",
     "sources": [
      "https://www.kubernetes.dev/docs/guide/pull-requests/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "meta",
     "name": "Meta",
     "sources": [
      "https://engineering.fb.com/2022/11/16/culture/meta-code-review-time-improving/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "stripe",
     "name": "Stripe",
     "sources": [
      "https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents"
     ],
     "strength": "unjudged"
    },
    {
     "id": "uber",
     "name": "Uber",
     "sources": [
      "https://www.uber.com/blog/efficient-software-factory/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "dropbox",
     "name": "Dropbox",
     "sources": [
      "https://dropbox.tech/culture/beyond-code-generation-rethinking-engineering-productivity-in-the-age-of-ai-agents"
     ],
     "strength": "unjudged"
    },
    {
     "id": "anthropic",
     "name": "Anthropic",
     "sources": [
      "https://www.anthropic.com/engineering/claude-code-best-practices"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/change#chg-agent-independent-review",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-approval-tied-to-patch",
   "level": "change",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "process"
   ],
   "topics": [
    "review"
   ],
   "lang": "core",
   "applies_when": "A review approval or verification result exists and the branch has been pushed or rebased since.",
   "context": [],
   "tool_gate": "Branch protection that dismisses stale approvals on new commits.",
   "note": "one vendor's engineering guidance (opinion), from poteto-mode/playbooks/shipping.md; patch-id comparison is the source's method; Anthropic (a vendor) adds \"A PR approval is only good for one commit\" with reapproval after an untrusted commit, but no patch-id carve-outs",
   "lacks": null,
   "rule": "Treat an approval or verification verdict as current only if the patch is unchanged: compare `git patch-id` at the verdict commit with the current head. If the patches differ only in tests, docs, or lint config, the verdict can stand when the built output is the same; any other difference needs a fresh check.",
   "why": "A verdict covers one patch. After a push or rebase the reviewer approved code that no longer exists.",
   "counterpoint": "\"Re-approving after every push is noisy.\" A rebase that keeps the patch id does not void the verdict, and neither does a change to tests, docs, or lint config that leaves the built output the same. Branch protection can enforce this without human effort.",
   "fail": {
    "language": "text",
    "code": "Approved at abc123. Two later pushes change src/auth.py. Merged on the old approval."
   },
   "pass": {
    "language": "text",
    "code": "Approved at abc123. Rebase onto main: `git patch-id` of the diff at abc123 equals the one at the new head, so the approval stands.\nPush that edits src/auth.py: patch ids differ, approval dismissed, re-requested.\nPush that only edits a README: approval stands once the build output is shown to be the same."
   },
   "sources": [
    "https://github.com/cursor/plugins/blob/main/pstack/skills/poteto-mode/playbooks/shipping.md",
    "https://x.com/addyosmani/status/2114604912520921136",
    "https://docs.gitlab.com/user/project/merge_requests/approvals/settings/",
    "https://docs.prow.k8s.io/docs/components/plugins/approve/approvers/",
    "https://gerrit-review.googlesource.com/Documentation/config-labels.html"
   ],
   "orgs": [
    {
     "id": "cursor",
     "name": "Cursor",
     "sources": [
      "https://github.com/cursor/plugins/blob/main/pstack/skills/poteto-mode/playbooks/shipping.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "anthropic",
     "name": "Anthropic",
     "sources": [
      "https://x.com/addyosmani/status/2114604912520921136"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://docs.gitlab.com/user/project/merge_requests/approvals/settings/"
     ],
     "strength": "supports"
    },
    {
     "id": "kubernetes",
     "name": "Kubernetes",
     "sources": [
      "https://docs.prow.k8s.io/docs/components/plugins/approve/approvers/"
     ],
     "strength": "supports"
    },
    {
     "id": "gerrit",
     "name": "Gerrit Code Review",
     "sources": [
      "https://gerrit-review.googlesource.com/Documentation/config-labels.html"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "strong",
   "url": "https://goodcodereview.com/change#chg-approval-tied-to-patch",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-author-hygiene",
   "level": "change",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "process"
   ],
   "topics": [
    "author",
    "ci"
   ],
   "lang": "core",
   "applies_when": "Every change submitted for review.",
   "context": [],
   "tool_gate": "PR template with required sections; branch protection requiring green CI.",
   "note": "Linux kernel and Kubernetes state the pre-submit self-check; GitLab states self-review of the merge request as a checklist item, and it is a vendor but this is contributor guidance. Phabricator covers the commit message only and Palantir is unreviewed",
   "lacks": null,
   "rule": "Self-review the diff and write the description before requesting review.",
   "why": "Reviewer time spent on stray debug lines, red builds, or a missing \"why\" is wasted and teaches reviewers to skim. The description gives the reason, the impact, and how the author verified the change. CI is green, or a failing job is shown to fail on the base branch too.",
   "counterpoint": "Templates breed boilerplate that no one reads. The check is for substance, not headings: a one-line change needs one line of reason. Performance claims need numbers.",
   "fail": {
    "language": "text",
    "code": "Title: \"fixes\". Body empty. CI red on lint. Diff has a leftover print() and\na commented-out block."
   },
   "pass": {
    "language": "text",
    "code": "Body: why (bug link), what changed, test plan, risk. CI green.\nAuthor left inline notes on the two non-obvious hunks."
   },
   "sources": [
    "https://docs.kernel.org/process/submitting-patches.html",
    "https://secure.phabricator.com/book/phabflavor/article/writing_reviewable_code/",
    "https://blog.palantir.com/code-review-best-practices-19e02780015f",
    "https://www.kubernetes.dev/docs/guide/pull-requests/",
    "https://docs.gitlab.com/development/code_review/"
   ],
   "orgs": [
    {
     "id": "kernel",
     "name": "Linux kernel",
     "sources": [
      "https://docs.kernel.org/process/submitting-patches.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "phacility",
     "name": "Phacility",
     "sources": [
      "https://secure.phabricator.com/book/phabflavor/article/writing_reviewable_code/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "palantir",
     "name": "Palantir",
     "sources": [
      "https://blog.palantir.com/code-review-best-practices-19e02780015f"
     ],
     "strength": "unjudged"
    },
    {
     "id": "kubernetes",
     "name": "Kubernetes",
     "sources": [
      "https://www.kubernetes.dev/docs/guide/pull-requests/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://docs.gitlab.com/development/code_review/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/change#chg-author-hygiene",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-breaking-change-taxonomy",
   "level": "change",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "compatibility"
   ],
   "topics": [
    "api"
   ],
   "lang": "core",
   "applies_when": "The change alters anything a caller can observe, documented or not.",
   "context": [],
   "tool_gate": "Contract tests and API-diff tooling.",
   "note": "Google AIP-180, Microsoft and Kubernetes cover observable behaviour and defaults. Python PEP 387 names return values, side effects and raised exceptions; OpenJDK treats a changed thrown error as a compatibility issue. The consumer check has AWS (contract tests on every producer change), Pact (can-i-deploy against known consumers; a vendor) and Spring Cloud Contract (a vendor). The Python devguide states nothing on this and does not count. Ordering and timing have no support, and Microsoft allows performance changes",
   "lacks": null,
   "rule": "Treat any change that callers can observe in a signature, return values, error types, defaults, or side effects as breaking until a consumer check says otherwise.",
   "why": "With enough users, every observable behaviour is depended on, so \"undocumented\" does not mean \"safe to change\". Result ordering and timing can break callers too, but no published guide treats them as breaking, so weigh them case by case.",
   "counterpoint": "Treating everything as breaking freezes the code. Weigh each change by known consumers and blast radius, and use a staged rollout instead of a ban. Optional parameters with defaults are the safe way to add.",
   "fail": {
    "language": "text",
    "code": "list_orders() now returns newest-first (was insertion order).\nPR says \"no API change\"; docs never promised an order."
   },
   "pass": {
    "language": "text",
    "code": "PR states: \"ordering changes, known consumers A and B checked,\nflag orders_newest_first defaults off for one release\"."
   },
   "sources": [
    "https://www.hyrumslaw.com/",
    "https://michaelfeathers.silvrback.com/characterization-testing",
    "https://devguide.python.org/getting-started/pull-request-lifecycle/",
    "https://google.aip.dev/180",
    "https://learn.microsoft.com/en-us/dotnet/core/compatibility/library-change-rules",
    "https://kubernetes.io/docs/reference/using-api/deprecation-policy/",
    "https://peps.python.org/pep-0387/",
    "https://wiki.openjdk.org/spaces/csr/pages/32342052/Kinds+of+Compatibility",
    "https://docs.aws.amazon.com/wellarchitected/latest/devops-guidance/qa.nt.7-verify-service-integrations-through-contract-testing.html",
    "https://docs.pact.io/pact_broker/can_i_deploy",
    "https://docs.spring.io/spring-cloud-contract/reference/project-features-stubrunner/stub-runner-stubs-per-consumer.html"
   ],
   "orgs": [
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://devguide.python.org/getting-started/pull-request-lifecycle/",
      "https://peps.python.org/pep-0387/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://google.aip.dev/180"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/dotnet/core/compatibility/library-change-rules"
     ],
     "strength": "unjudged"
    },
    {
     "id": "kubernetes",
     "name": "Kubernetes",
     "sources": [
      "https://kubernetes.io/docs/reference/using-api/deprecation-policy/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "openjdk",
     "name": "OpenJDK",
     "sources": [
      "https://wiki.openjdk.org/spaces/csr/pages/32342052/Kinds+of+Compatibility"
     ],
     "strength": "unjudged"
    },
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://docs.aws.amazon.com/wellarchitected/latest/devops-guidance/qa.nt.7-verify-service-integrations-through-contract-testing.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "pact",
     "name": "Pact",
     "sources": [
      "https://docs.pact.io/pact_broker/can_i_deploy"
     ],
     "strength": "unjudged"
    },
    {
     "id": "spring",
     "name": "Spring",
     "sources": [
      "https://docs.spring.io/spring-cloud-contract/reference/project-features-stubrunner/stub-runner-stubs-per-consumer.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "hyrum",
     "name": "Hyrum Wright",
     "sources": [
      "https://www.hyrumslaw.com/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "feathers",
     "name": "Michael Feathers",
     "sources": [
      "https://michaelfeathers.silvrback.com/characterization-testing"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/change#chg-breaking-change-taxonomy",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-chestertons-fence",
   "level": "change",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "maintainability",
    "process"
   ],
   "topics": [
    "legacy",
    "history"
   ],
   "lang": "core",
   "applies_when": "The change removes, rewrites or \"simplifies\" odd-looking existing code, or rewrites a module.",
   "context": [],
   "tool_gate": null,
   "note": null,
   "lacks": null,
   "rule": "Add a characterization test before rewriting code you do not understand.",
   "why": "Strange code often carries a fix for a bug that is no longer visible. Removing it re-ships the bug. Check the history first (blame, the PR that added it, tickets, postmortems); defensive code such as null checks, retries and timeouts usually answers a real incident. For a rewrite, name the defect that justifies it.",
   "counterpoint": "Joel Spolsky's anti-rewrite case rests on anecdotes (Netscape, Borland), and Hickey argues for restating the problem, which may mean a new design. Rethinking is allowed. A big-bang rewrite needs small shippable steps, the old path working meanwhile, and a named defect.",
   "fail": {
    "language": "text",
    "code": "PR \"simplify retry loop\": removes sleep(0.2) and a dedup check.\nNo blame/history cited; no test. Rewrite justified by \"this is a mess\"."
   },
   "pass": {
    "language": "text",
    "code": "git blame shows sleep added for rate-limit incident INC-17.\nPR keeps it with a comment, adds a characterization test first."
   },
   "sources": [
    "https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/",
    "https://michaelfeathers.silvrback.com/characterization-testing",
    "https://github.com/cursor/plugins/blob/main/pstack/skills/why/SKILL.md",
    "https://github.com/cursor/plugins/blob/main/dyl-stack/skills/dyl-review/SKILL.md",
    "https://getyourguide.careers/posts/how-we-work-efficiently-with-legacy-code",
    "https://www.redhat.com/en/blog/refactoring-speed-mission-agent-mesh-approach-legacy-system-modernization-red-hat-ai"
   ],
   "orgs": [
    {
     "id": "cursor",
     "name": "Cursor",
     "sources": [
      "https://github.com/cursor/plugins/blob/main/pstack/skills/why/SKILL.md",
      "https://github.com/cursor/plugins/blob/main/dyl-stack/skills/dyl-review/SKILL.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "getyourguide",
     "name": "GetYourGuide",
     "sources": [
      "https://getyourguide.careers/posts/how-we-work-efficiently-with-legacy-code"
     ],
     "strength": "supports"
    },
    {
     "id": "redhat",
     "name": "Red Hat",
     "sources": [
      "https://www.redhat.com/en/blog/refactoring-speed-mission-agent-mesh-approach-legacy-system-modernization-red-hat-ai"
     ],
     "strength": "supports"
    }
   ],
   "people": [
    {
     "id": "joel",
     "name": "Joel Spolsky",
     "sources": [
      "https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "feathers",
     "name": "Michael Feathers",
     "sources": [
      "https://michaelfeathers.silvrback.com/characterization-testing"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "mixed",
   "url": "https://goodcodereview.com/change#chg-chestertons-fence",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-delete-legacy-after-migration",
   "level": "change",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "compatibility",
    "maintainability"
   ],
   "topics": [
    "migration",
    "cleanup"
   ],
   "lang": "core",
   "applies_when": "The change adds a new API, path or config and every caller lives in this repo.",
   "context": [
    "data"
   ],
   "tool_gate": "Dead-code and unused-export checks.",
   "note": "cursor/plugins and Stripe (one-PR Flow to TypeScript move) back the Rule; Uber's JUnit move ran old and new side by side, so it does not; applies only without outside consumers",
   "lacks": null,
   "rule": "When no consumer sits outside the repo, move every caller to the new API and delete the old one, and the tests that pinned its internals, in the same change.",
   "why": "Two supported ways to do one task both keep gaining callers. The old path becomes the one nobody dares remove.",
   "counterpoint": "This rule applies only when no consumer sits outside the repo: a published library, a public API, a client on an older release, or data written by older code. For those, follow [[chg-expand-contract]] and remove the old form later. Check the consumer claim before you mark `FAIL`. Even with all callers in the repo, a team may keep the old path on purpose: Stripe kept Flow support for part of its codebase while it moved the rest to TypeScript. A stated, scoped exception is not a FAIL.",
   "fail": {
    "language": "text",
    "code": "+ def fetch_user_v2(id): ...\n  def fetch_user(id): ...        # kept \"for now\"; 14 callers still use it"
   },
   "pass": {
    "language": "text",
    "code": "- def fetch_user(id): ...        # old API deleted\n+ def fetch_user_v2(id): ...     # new API\n- user = fetch_user(uid)         # x14, every caller moved to fetch_user_v2 in this diff\n+ user = fetch_user_v2(uid)"
   },
   "sources": [
    "https://github.com/cursor/plugins/blob/main/pstack/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md",
    "https://github.com/cursor/plugins/blob/main/pstack/skills/architect/references/design-red-flags.md",
    "https://github.com/cursor/plugins/blob/main/pstack/skills/interrogate/references/rubric.md",
    "https://stripe.dev/blog/migrating-to-typescript",
    "https://research.google/pubs/large-scale-automated-refactoring-using-clangmr",
    "https://llvm.org/docs/SupportPolicy.html"
   ],
   "orgs": [
    {
     "id": "cursor",
     "name": "Cursor",
     "sources": [
      "https://github.com/cursor/plugins/blob/main/pstack/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md",
      "https://github.com/cursor/plugins/blob/main/pstack/skills/architect/references/design-red-flags.md",
      "https://github.com/cursor/plugins/blob/main/pstack/skills/interrogate/references/rubric.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "stripe",
     "name": "Stripe",
     "sources": [
      "https://stripe.dev/blog/migrating-to-typescript"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://research.google/pubs/large-scale-automated-refactoring-using-clangmr"
     ],
     "strength": "supports"
    },
    {
     "id": "llvm",
     "name": "LLVM",
     "sources": [
      "https://llvm.org/docs/SupportPolicy.html"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/change#chg-delete-legacy-after-migration",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-devex-breaking-config",
   "level": "change",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "compatibility",
    "process"
   ],
   "topics": [
    "config"
   ],
   "lang": "core",
   "applies_when": "The change renames an environment variable, remaps a port, moves a secret source, or adds a step needed to run or build the project.",
   "context": [],
   "tool_gate": "Startup check that fails fast on missing or unknown config keys.",
   "note": "from one vendor's review skill (opinion); the rule is the narrow, checkable part",
   "lacks": null,
   "rule": "Flag a change that renames or removes an environment variable, moves a secret, or remaps a port as breaking for developers and operators, and update every place that documents or sets it.",
   "why": "A renamed variable or moved secret fails on the next machine that pulls, often with a vague error far from the cause. The diff looks harmless because nothing in the code breaks. A short migration note with the old name, the new name, and what to do saves each person from finding that out alone.",
   "counterpoint": "\"It is an internal repo; people will ask.\" Each person who asks costs more than the note, and CI and deploy environments cannot ask. For published configuration, [[chg-breaking-change-taxonomy]] has the wider rules.",
   "fail": {
    "language": "text",
    "code": "- DATABASE_URL=...\n+ DB_DSN=...            # no note, no fallback, .env.example untouched"
   },
   "pass": {
    "language": "text",
    "code": "+ DB_DSN=...            # reads DATABASE_URL with a deprecation warning for one release\n+ PR text flags the break: \"DATABASE_URL renamed to DB_DSN; update .env and CI secrets\"\n+ .env.example updated"
   },
   "sources": [
    "https://github.com/cursor/plugins/blob/main/thermos/skills/thermo-nuclear-review/SKILL.md",
    "https://docs.gitlab.com/administration/package_information/deprecation_policy",
    "https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide"
   ],
   "orgs": [
    {
     "id": "cursor",
     "name": "Cursor",
     "sources": [
      "https://github.com/cursor/plugins/blob/main/thermos/skills/thermo-nuclear-review/SKILL.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://docs.gitlab.com/administration/package_information/deprecation_policy"
     ],
     "strength": "supports"
    },
    {
     "id": "spring",
     "name": "Spring",
     "sources": [
      "https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/change#chg-devex-breaking-config",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-expand-contract",
   "level": "change",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "compatibility"
   ],
   "topics": [
    "migration"
   ],
   "lang": "core",
   "applies_when": "The change renames, removes or reshapes a column, API field, message, config key or exported symbol that other code reads.",
   "context": [
    "data"
   ],
   "tool_gate": "Schema or API diff tools (breaking-change detectors) in CI.",
   "note": "GitLab and Stripe state migrating every consumer before the old path goes; Microsoft's six-phase post states expand, move writes, data and reads, validate, then contract. Kubernetes states add-both-versions-then-remove for API versions, not expand-contract by name. Config keys and exported symbols have no source",
   "lacks": null,
   "rule": "Evolve shared contracts in steps: add the new form beside the old (expand), migrate every consumer, then remove the old form (contract) in a later change.",
   "why": "Producers and consumers deploy at different times. A one-step rename breaks whichever side is still on the old version.",
   "counterpoint": "Three steps are slow for internal code. Exception: when producer and consumers deploy atomically from one repo, or the table is small enough to alter in one safe step, a one-step change is fine. Say so in the description. [[sys-migration-expand-contract]] covers batching the backfill.",
   "fail": {
    "language": "sql",
    "code": "ALTER TABLE users RENAME COLUMN name TO full_name;  -- app v1 still reads name"
   },
   "pass": {
    "language": "sql",
    "code": "-- change 1: add full_name, dual-write, backfill\n-- change 2: readers switch to full_name\n-- change 3 (later): drop name"
   },
   "sources": [
    "https://www.hyrumslaw.com/",
    "https://devguide.python.org/getting-started/pull-request-lifecycle/",
    "https://docs.gitlab.com/development/database/avoiding_downtime_in_migrations/",
    "https://stripe.com/blog/online-migrations",
    "https://kubernetes.io/docs/reference/using-api/deprecation-policy/",
    "https://devblogs.microsoft.com/azure-sql/schema-change/"
   ],
   "orgs": [
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://devguide.python.org/getting-started/pull-request-lifecycle/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://docs.gitlab.com/development/database/avoiding_downtime_in_migrations/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "stripe",
     "name": "Stripe",
     "sources": [
      "https://stripe.com/blog/online-migrations"
     ],
     "strength": "unjudged"
    },
    {
     "id": "kubernetes",
     "name": "Kubernetes",
     "sources": [
      "https://kubernetes.io/docs/reference/using-api/deprecation-policy/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://devblogs.microsoft.com/azure-sql/schema-change/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "hyrum",
     "name": "Hyrum Wright",
     "sources": [
      "https://www.hyrumslaw.com/"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/change#chg-expand-contract",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-match-codebase-pattern",
   "level": "change",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "correctness",
    "maintainability"
   ],
   "topics": [
    "consistency",
    "style"
   ],
   "lang": "core",
   "applies_when": "The change adds code that solves a problem the codebase already solves somewhere.",
   "context": [],
   "tool_gate": "Repeated style nits mean a missing linter or formatter rule; add the gate instead of commenting.",
   "note": "Rule backed by Google (eng-practices standard), PEP 8, and the Microsoft engineering playbook; the Orosz post is inferred support only",
   "lacks": null,
   "rule": "Follow the established pattern nearby.",
   "why": "A silent second way to do the same thing doubles what every later reader must learn. Equally, copying a bad pattern forever freezes it in place.",
   "counterpoint": "\"Consistency preserves bugs.\" Consistency is a tie-breaker, not a reason to copy something that breaks a rule or hurts code health, so a reviewer must not demand matching code that makes things worse. When the nearby pattern is bad, keep to the local style in this change, file a ticket, and propose the new standard in its own change, improving what you touch. A second pattern slipped into a feature change without a word is the failure.",
   "fail": {
    "language": "text",
    "code": "Repo uses Result-returning repository classes everywhere.\nNew PR adds a module that raises raw DB exceptions and builds SQL inline,\ndescription: \"cleaner this way\". No ticket, no mention of the old pattern."
   },
   "pass": {
    "language": "text",
    "code": "New module uses the repository pattern like its neighbours.\nPR notes: \"pattern X leaks DB errors, see TICKET-123 proposing a standard\".\nCode the PR touches anyway is improved in passing."
   },
   "sources": [
    "https://google.github.io/eng-practices/review/reviewer/standard.html",
    "https://blog.pragmaticengineer.com/good-code-reviews-better-code-reviews/",
    "https://peps.python.org/pep-0008/",
    "https://microsoft.github.io/code-with-engineering-playbook/code-reviews/process-guidance/reviewer-guidance/"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://google.github.io/eng-practices/review/reviewer/standard.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "pragmatic-engineer",
     "name": "The Pragmatic Engineer",
     "sources": [
      "https://blog.pragmaticengineer.com/good-code-reviews-better-code-reviews/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://peps.python.org/pep-0008/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://microsoft.github.io/code-with-engineering-playbook/code-reviews/process-guidance/reviewer-guidance/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/change#chg-match-codebase-pattern",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-migration-add-column-rewrite",
   "level": "change",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "compatibility"
   ],
   "topics": [
    "database",
    "migration",
    "postgres"
   ],
   "lang": "core",
   "applies_when": "A migration adds a column to a large PostgreSQL table.",
   "context": [
    "data"
   ],
   "tool_gate": "Migration linter that flags volatile defaults, stored generated columns and identity columns on ADD COLUMN.",
   "note": null,
   "lacks": null,
   "rule": "Add the column nullable or with a constant default, then fill volatile, computed, or identity values in batches.",
   "why": "A constant or NULL default is a metadata change. A volatile default, a stored generated column, or an identity column rewrites the whole table, and the default lock (ACCESS EXCLUSIVE) blocks every query on it for the duration.",
   "counterpoint": "\"The table is small.\" Then the rewrite is cheap and the one-step form is fine. State the row count in the description. [[sys-migration-lock-timeout]] covers locking and timeouts.",
   "fail": {
    "language": "sql",
    "code": "ALTER TABLE events ADD COLUMN token uuid DEFAULT gen_random_uuid();   -- volatile: rewrites 400M rows"
   },
   "pass": {
    "language": "sql",
    "code": "ALTER TABLE events ADD COLUMN token uuid;                 -- no rewrite\n-- backfill in batches, then SET DEFAULT for new rows only"
   },
   "sources": [
    "https://www.postgresql.org/docs/current/sql-altertable.html",
    "https://medium.com/paypal-tech/postgresql-at-scale-database-schema-changes-without-downtime-20d3749ed680",
    "https://retool.com/blog/running-safe-database-migrations-using-postgres"
   ],
   "orgs": [
    {
     "id": "postgresql",
     "name": "PostgreSQL",
     "sources": [
      "https://www.postgresql.org/docs/current/sql-altertable.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "paypal",
     "name": "PayPal",
     "sources": [
      "https://medium.com/paypal-tech/postgresql-at-scale-database-schema-changes-without-downtime-20d3749ed680"
     ],
     "strength": "supports"
    },
    {
     "id": "retool",
     "name": "Retool",
     "sources": [
      "https://retool.com/blog/running-safe-database-migrations-using-postgres"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/change#chg-migration-add-column-rewrite",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-name-attack-surface",
   "level": "change",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "security",
    "process"
   ],
   "topics": [
    "review"
   ],
   "lang": "core",
   "applies_when": "The change adds an endpoint, input parser, file upload, dependency, permission, secret or outbound call.",
   "context": [],
   "tool_gate": "SAST and dependency scanning in CI catch the mechanical part.",
   "note": "thin sourcing: routing to a qualified reviewer and crypto randomness only; the rest is synthesis",
   "lacks": null,
   "rule": "List any new attack surface in the description.",
   "why": "A generalist reviewing a diff will not spot an exposed route the author never mentioned. A new input, trust boundary, credential, or dependency counts, and security-relevant changes go to a qualified reviewer.",
   "counterpoint": "Naming threats on every PR is theatre. Only changes that add one of the listed surfaces need the section, and most PRs write \"none\".",
   "fail": {
    "language": "text",
    "code": "PR \"add debug endpoint /admin/dump\". Description: \"helper for support\".\nAuth not mentioned, merged by one general reviewer."
   },
   "pass": {
    "language": "text",
    "code": "Description: \"new endpoint, admin-only via role X, rate limited, no PII\".\nSecurity reviewer tagged. Randomness uses a crypto source."
   },
   "sources": [
    "https://google.github.io/eng-practices/review/reviewer/looking-for.html",
    "https://go.dev/wiki/CodeReviewComments",
    "https://cheatsheetseries.owasp.org/cheatsheets/Attack_Surface_Analysis_Cheat_Sheet.html",
    "https://wiki.mozilla.org/Security/Reviews",
    "https://peps.python.org/pep-0012/"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://google.github.io/eng-practices/review/reviewer/looking-for.html",
      "https://go.dev/wiki/CodeReviewComments"
     ],
     "strength": "unjudged"
    },
    {
     "id": "owasp",
     "name": "OWASP",
     "sources": [
      "https://cheatsheetseries.owasp.org/cheatsheets/Attack_Surface_Analysis_Cheat_Sheet.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://wiki.mozilla.org/Security/Reviews"
     ],
     "strength": "unjudged"
    },
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://peps.python.org/pep-0012/"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/change#chg-name-attack-surface",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-no-silenced-diagnostics",
   "level": "change",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "correctness",
    "maintainability",
    "process"
   ],
   "topics": [
    "errors",
    "lint",
    "ci"
   ],
   "lang": "core",
   "applies_when": "The diff adds a suppression (lint ignore, type ignore, skipped test, swallowed error) or deletes a failing check.",
   "context": [],
   "tool_gate": "Linter rule that requires a reason and ticket on each suppression.",
   "note": "Palantir URL returned 403 and was not verified; extends Palantir (TODO without issue, red checks) and Go (discarded errors) to suppressions",
   "lacks": null,
   "rule": "Name the rule, the reason, and a ticket on every suppression; a swallowed error, skipped test, or disabled check is a FAIL unless justified.",
   "why": "Silencing a diagnostic hides the problem it reports and removes the safety net for the next author.",
   "counterpoint": "Sometimes a warning is a false positive. Then suppress narrowly with a reason and an expiry date, which keeps the gate useful. A suppression of a rule that guards correctness or safety should be removed, not documented. Palantir's list covers TODOs without issue references and merging with checks red. The Go list covers discarded errors. This card extends both to suppressions.",
   "fail": {
    "language": "python",
    "code": "x = load(cfg)  # type: ignore\ntry: sync()\nexcept Exception: pass\n@pytest.mark.skip   # flaky"
   },
   "pass": {
    "language": "python",
    "code": "x = load(cfg)  # type: ignore[arg-type]  # lib stub wrong, TICKET-42\ntry: sync()\n@pytest.mark.skip(reason=\"flaky on CI clock skew, TICKET-43, remove by 2026-12-01\")"
   },
   "sources": [
    "https://go.dev/wiki/CodeReviewComments",
    "https://blog.palantir.com/code-review-best-practices-19e02780015f",
    "https://github.com/cursor/plugins/blob/main/pstack/skills/correct/SKILL.md",
    "https://github.com/cursor/plugins/blob/main/pstack/skills/no-comments/SKILL.md",
    "https://eslint.org/docs/latest/use/configure/rules",
    "https://rust-lang.github.io/rust-clippy/master/index.html#allow_attributes_without_reason"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://go.dev/wiki/CodeReviewComments"
     ],
     "strength": "unjudged"
    },
    {
     "id": "palantir",
     "name": "Palantir",
     "sources": [
      "https://blog.palantir.com/code-review-best-practices-19e02780015f"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cursor",
     "name": "Cursor",
     "sources": [
      "https://github.com/cursor/plugins/blob/main/pstack/skills/correct/SKILL.md",
      "https://github.com/cursor/plugins/blob/main/pstack/skills/no-comments/SKILL.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "eslint",
     "name": "ESLint",
     "sources": [
      "https://eslint.org/docs/latest/use/configure/rules"
     ],
     "strength": "supports"
    },
    {
     "id": "rust",
     "name": "Rust project",
     "sources": [
      "https://rust-lang.github.io/rust-clippy/master/index.html#allow_attributes_without_reason"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/change#chg-no-silenced-diagnostics",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-no-special-case-branches",
   "level": "change",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "design",
    "complexity"
   ],
   "lang": "core",
   "applies_when": "The change adds a conditional, flag or nullable mode to code that serves more than one feature.",
   "context": [],
   "tool_gate": "Microsoft CA1502 (cyclomatic complexity, default 25) or PMD CognitiveComplexity (default 15) on the shared function.",
   "note": "own function or type - Linux kernel (factor the conditional into a helper function), Effective Go (a type switch, a switch for an if-else chain), Shopify (move the branch onto the item); cap on branches - Microsoft CA1502, PMD CognitiveComplexity; Cursor review rubric is the origin",
   "lacks": null,
   "rule": "Put a new case behind its own function or type; do not add a one-off branch to a shared flow.",
   "why": "Each special case in a shared path is a condition that every later reader and every other feature must reason about. The cases accumulate until nobody can say what the path does.",
   "counterpoint": "\"It is one line; an abstraction is overkill.\" One is fine. The second and third special case are the signal. See [[mod-yagni]] for when not to extract yet..",
   "fail": {
    "language": "text",
    "code": "def checkout(cart, user):\n    ...\n    if user.org == \"acme\":            # added for one customer\n        total = total * 0.9"
   },
   "pass": {
    "language": "text",
    "code": "def checkout(cart, user, pricing=standard_pricing):\n    total = pricing(cart, user)       # acme's rule lives in its own pricing function"
   },
   "sources": [
    "https://www.kernel.org/doc/html/latest/process/coding-style.html",
    "https://go.dev/doc/effective_go",
    "https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1502",
    "https://pmd.github.io/pmd/pmd_rules_java_design.html",
    "https://shopify.engineering/other-driven-developments",
    "https://github.com/cursor/plugins/blob/main/thermos/skills/thermo-nuclear-code-quality-review/SKILL.md"
   ],
   "orgs": [
    {
     "id": "kernel",
     "name": "Linux kernel",
     "sources": [
      "https://www.kernel.org/doc/html/latest/process/coding-style.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://go.dev/doc/effective_go"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1502"
     ],
     "strength": "unjudged"
    },
    {
     "id": "pmd",
     "name": "PMD",
     "sources": [
      "https://pmd.github.io/pmd/pmd_rules_java_design.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "shopify",
     "name": "Shopify",
     "sources": [
      "https://shopify.engineering/other-driven-developments"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cursor",
     "name": "Cursor",
     "sources": [
      "https://github.com/cursor/plugins/blob/main/thermos/skills/thermo-nuclear-code-quality-review/SKILL.md"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/change#chg-no-special-case-branches",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-one-concern",
   "level": "change",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "process"
   ],
   "topics": [
    "scope",
    "size"
   ],
   "lang": "core",
   "applies_when": "Every change.",
   "context": [],
   "tool_gate": "A CI check can label or warn on file count and diff size.",
   "note": null,
   "lacks": null,
   "rule": "Make each change do one logical thing; if the description needs \"and\" between unrelated outcomes, split it.",
   "why": "Reviewers can only judge intent and risk of a single concern. Mixed changes hide the risky part, cannot be reverted alone, and break bisect.",
   "counterpoint": "Splitting adds overhead, and stacked diffs need tooling and cost rebasing pain. The rule is one concern, not a line count: as small as possible, but no smaller. A single concern that is legitimately large (a generated migration) is fine if the description says so.",
   "fail": {
    "language": "text",
    "code": "PR \"Add export button\": new endpoint, renamed 40 files, bumped a dependency,\nfixed an unrelated typo in the auth module."
   },
   "pass": {
    "language": "text",
    "code": "PR 1: dependency bump.  PR 2: rename (no logic change).  PR 3: export button."
   },
   "sources": [
    "https://docs.kernel.org/process/submitting-patches.html",
    "https://devguide.python.org/getting-started/pull-request-lifecycle/",
    "https://secure.phabricator.com/book/phabflavor/article/writing_reviewable_code/",
    "https://blog.palantir.com/code-review-best-practices-19e02780015f"
   ],
   "orgs": [
    {
     "id": "kernel",
     "name": "Linux kernel",
     "sources": [
      "https://docs.kernel.org/process/submitting-patches.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://devguide.python.org/getting-started/pull-request-lifecycle/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "phacility",
     "name": "Phacility",
     "sources": [
      "https://secure.phabricator.com/book/phabflavor/article/writing_reviewable_code/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "palantir",
     "name": "Palantir",
     "sources": [
      "https://blog.palantir.com/code-review-best-practices-19e02780015f"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/change#chg-one-concern",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-perf-claim-evidence",
   "level": "change",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "performance",
    "testing"
   ],
   "topics": [
    "verification"
   ],
   "lang": "core",
   "applies_when": "The change or its description claims a speed, throughput, memory or cost improvement.",
   "context": [],
   "tool_gate": null,
   "note": "the kernel, Phabricator and GitLab (measure again after deploy, report percentiles) back the Rule (numbers for an optimisation claim); the run count, spread and limiter in the Why come from benchmark-checklist/SKILL.md",
   "lacks": null,
   "rule": "Back a performance claim with measured before-and-after numbers.",
   "why": "A single fast run can measure noise, a cached result, an error path that returns early, or a step that is a tenth of the total. A piece that is 10% of the run caps the gain near 11% however well it is tuned. So a useful number comes with the run count, the spread, the resource that limits further gain, and a check that errors were counted and the work ran inside the timed region.",
   "counterpoint": "\"A micro-optimisation needs no benchmark ceremony.\" A claim in prose does. If the description makes no performance claim, mark `N/A`.",
   "fail": {
    "language": "text",
    "code": "\"Parse is much faster now.\" (no numbers; 4% of requests now return 500 early)"
   },
   "pass": {
    "language": "text",
    "code": "Median of 7 alternating runs: 412 ms -> 301 ms, range 398-421 vs 295-310.\n0 errors in both. Parse is 35% of the request, so the cap is 1.5x; limiter: DB round trip."
   },
   "sources": [
    "https://github.com/cursor/plugins/blob/main/pstack/skills/principle-explain-the-number/SKILL.md",
    "https://github.com/cursor/plugins/blob/main/pstack/skills/benchmark-checklist/SKILL.md",
    "https://github.com/cursor/plugins/blob/main/cursor-team-kit/skills/verify-this/SKILL.md",
    "https://docs.kernel.org/process/submitting-patches.html",
    "https://docs.gitlab.com/development/performance/",
    "https://secure.phabricator.com/book/phabflavor/article/writing_reviewable_code/"
   ],
   "orgs": [
    {
     "id": "cursor",
     "name": "Cursor",
     "sources": [
      "https://github.com/cursor/plugins/blob/main/pstack/skills/principle-explain-the-number/SKILL.md",
      "https://github.com/cursor/plugins/blob/main/pstack/skills/benchmark-checklist/SKILL.md",
      "https://github.com/cursor/plugins/blob/main/cursor-team-kit/skills/verify-this/SKILL.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "kernel",
     "name": "Linux kernel",
     "sources": [
      "https://docs.kernel.org/process/submitting-patches.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://docs.gitlab.com/development/performance/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "phacility",
     "name": "Phacility",
     "sources": [
      "https://secure.phabricator.com/book/phabflavor/article/writing_reviewable_code/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/change#chg-perf-claim-evidence",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-proto-no-field-type-change",
   "level": "change",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "compatibility"
   ],
   "topics": [
    "api",
    "protobuf"
   ],
   "lang": "core",
   "applies_when": "The diff changes an existing field's type, default, label or name in a `.proto` file.",
   "context": [],
   "tool_gate": "`buf breaking` with the wire and JSON rule sets.",
   "note": null,
   "lacks": null,
   "rule": "Never change the type of a protobuf field, switch it between repeated and scalar, or rename it while any consumer reads JSON or text format; add a new field instead.",
   "why": "The wire format carries numbers and types, not names, so a type change is read as garbage by anything not yet redeployed. The JSON and text formats carry names, so a rename breaks those readers.",
   "counterpoint": "\"Both ends deploy together.\" A rolling deploy runs both versions at once, and stored data does not redeploy. If the message never leaves one binary, mark the card N/A.",
   "fail": {
    "language": "proto",
    "code": "message Invoice {\n-  int32 amount = 2;\n+  string amount = 2;\n}"
   },
   "pass": {
    "language": "proto",
    "code": "message Invoice {\n  int32 amount = 2 [deprecated = true];\n  string amount_decimal = 3;\n}"
   },
   "sources": [
    "https://protobuf.dev/best-practices/dos-donts/",
    "https://buf.build/docs/breaking/rules/",
    "https://github.com/istio/api/blob/master/GUIDELINES.md"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://protobuf.dev/best-practices/dos-donts/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "buf",
     "name": "Buf",
     "sources": [
      "https://buf.build/docs/breaking/rules/"
     ],
     "strength": "supports"
    },
    {
     "id": "istio",
     "name": "Istio",
     "sources": [
      "https://github.com/istio/api/blob/master/GUIDELINES.md"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/change#chg-proto-no-field-type-change",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-proto-reserve-removed-fields",
   "level": "change",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "compatibility"
   ],
   "topics": [
    "api",
    "protobuf"
   ],
   "lang": "core",
   "applies_when": "The diff deletes a field or enum value from a `.proto` message or enum.",
   "context": [],
   "tool_gate": "`buf breaking` or an equivalent descriptor diff in CI.",
   "note": null,
   "lacks": null,
   "rule": "When you delete a protobuf field or enum value, add `reserved` for its number and name in the same change, and never reuse a number.",
   "why": "Old data and old clients still carry the number. A new field that takes it is read as the old type, and the failure is silent corruption, not an error.",
   "counterpoint": "\"Nobody sends field 4 any more.\" Stored messages, queued messages and a client on last month's build do. Reserving costs one line and cannot be undone by accident.",
   "fail": {
    "language": "proto",
    "code": "message Order {\n-  string coupon = 4;\n+  int32 loyalty_points = 4;   // number 4 reused\n}"
   },
   "pass": {
    "language": "proto",
    "code": "message Order {\n  reserved 4;\n  reserved \"coupon\";\n  int32 loyalty_points = 5;\n}"
   },
   "sources": [
    "https://protobuf.dev/best-practices/dos-donts/",
    "https://learn.microsoft.com/en-us/aspnet/core/grpc/versioning",
    "https://deliveroo.engineering/2019/02/05/improving-stream-data-quality-with-protobuf-schema-validation.html"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://protobuf.dev/best-practices/dos-donts/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/aspnet/core/grpc/versioning"
     ],
     "strength": "supports"
    },
    {
     "id": "deliveroo",
     "name": "Deliveroo",
     "sources": [
      "https://deliveroo.engineering/2019/02/05/improving-stream-data-quality-with-protobuf-schema-validation.html"
     ],
     "strength": "supports"
    }
   ],
   "people": [],
   "backing": "mixed",
   "url": "https://goodcodereview.com/change#chg-proto-reserve-removed-fields",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-refactor-has-behaviour-pin",
   "level": "change",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "testing",
    "maintainability"
   ],
   "topics": [
    "refactoring",
    "tests"
   ],
   "lang": "core",
   "applies_when": "The change restructures code and claims to keep its behaviour.",
   "context": [],
   "tool_gate": "Snapshot or golden-file tests in CI.",
   "note": "Sonar and JetBrains (vendors) say to add tests that describe current behaviour, or characterization tests, before refactoring; Apache Flex says never to refactor without tests. Rule also backed by cursor/plugins (refactoring playbook), GitHub (existing visual regression snapshots matched before and after the CSS Modules move) and Stripe (a tool diffed parse trees across files rubyfmt formatted)",
   "lacks": null,
   "rule": "Pin current behaviour with a characterization test, snapshot or equivalence check before the refactor moves any structure.",
   "why": "A type check and a linter pass on code that now does something else. Without a pin, \"no behaviour change\" is a claim nobody ran.",
   "counterpoint": "\"The code is already well tested.\" Then name the test that covers the moved paths. If you cannot, the pin is missing. See [[chg-refactor-separate]] for keeping the refactor apart from behaviour changes.",
   "fail": {
    "language": "text",
    "code": "PR \"split billing.py into three modules\": 900 lines moved, no test touched or added."
   },
   "pass": {
    "language": "text",
    "code": "Commit 1: golden-file test of invoice output for 12 fixtures (passes on old code).\nCommit 2: the split. The same test passes unchanged."
   },
   "sources": [
    "https://github.com/cursor/plugins/blob/main/pstack/skills/poteto-mode/playbooks/refactoring.md",
    "https://github.blog/engineering/architecture-optimization/improving-site-performance-by-shipping-more-css",
    "https://stripe.dev/blog/formatting-an-entire-25-million-line-codebase-overnight-the-rubyfmt-story",
    "https://www.sonarsource.com/blog/legacy-codebases-are-a-devops-issue/",
    "https://junie.jetbrains.com/blog/work-with-legacy-code-efficiently/",
    "https://flex.apache.org/flexunit/tutorial/flexunit/Unit-15.html"
   ],
   "orgs": [
    {
     "id": "cursor",
     "name": "Cursor",
     "sources": [
      "https://github.com/cursor/plugins/blob/main/pstack/skills/poteto-mode/playbooks/refactoring.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://github.blog/engineering/architecture-optimization/improving-site-performance-by-shipping-more-css"
     ],
     "strength": "unjudged"
    },
    {
     "id": "stripe",
     "name": "Stripe",
     "sources": [
      "https://stripe.dev/blog/formatting-an-entire-25-million-line-codebase-overnight-the-rubyfmt-story"
     ],
     "strength": "unjudged"
    },
    {
     "id": "sonarsource",
     "name": "SonarSource",
     "sources": [
      "https://www.sonarsource.com/blog/legacy-codebases-are-a-devops-issue/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "jetbrains",
     "name": "JetBrains",
     "sources": [
      "https://junie.jetbrains.com/blog/work-with-legacy-code-efficiently/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "apache",
     "name": "Apache Software Foundation",
     "sources": [
      "https://flex.apache.org/flexunit/tutorial/flexunit/Unit-15.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/change#chg-refactor-has-behaviour-pin",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-refactor-separate",
   "level": "change",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "maintainability",
    "process"
   ],
   "topics": [
    "refactoring",
    "scope"
   ],
   "lang": "core",
   "applies_when": "The change both restructures or reformats code and changes behaviour, or moves code between files.",
   "context": [],
   "tool_gate": "A formatter check in CI removes the reformatting case.",
   "note": "Reformatting in its own commit is backed by Google, Apache Flink and LLVM, and the Python devguide also backs it. Renames rest on Google and Apache Flink; LLVM does not mention them. The kernel says one logical change per patch, which covers refactors only by extension",
   "lacks": null,
   "rule": "Put refactors, renames, and reformatting in their own commit or PR, apart from behaviour changes.",
   "why": "A reviewer can verify a pure refactor by \"tests unchanged\" and a pure move by diff. Mixed in with logic, neither check works and a real bug hides in noise. Never edit moved code in the same patch as the move.",
   "counterpoint": "Separate PRs cost review round trips, and untangling a mixed branch after the fact is work. Separate commits in one PR are an acceptable compromise, but separate PRs are ideal. A tidy that exists only to enable the behaviour change in hand may ship with it if the commits stay apart.",
   "fail": {
    "language": "text",
    "code": "commit: \"Move parser to parsing/ and fix off-by-one\"\ndiff shows whole file as new, the fix is line 212 of 400."
   },
   "pass": {
    "language": "text",
    "code": "commit 1: move parser (git detects rename, 0 content change)\ncommit 2: fix off-by-one + test"
   },
   "sources": [
    "https://docs.kernel.org/process/submitting-patches.html",
    "https://henrikwarne.com/2024/01/10/tidy-first/",
    "https://newsletter.kentbeck.com/p/getting-untangled",
    "https://google.github.io/eng-practices/review/reviewer/looking-for.html",
    "https://google.github.io/eng-practices/review/developer/small-cls.html",
    "https://flink.apache.org/how-to-contribute/code-style-and-quality-pull-requests/",
    "https://devguide.python.org/getting-started/pull-request-lifecycle/",
    "https://llvm.org/docs/Contributing.html"
   ],
   "orgs": [
    {
     "id": "kernel",
     "name": "Linux kernel",
     "sources": [
      "https://docs.kernel.org/process/submitting-patches.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://google.github.io/eng-practices/review/reviewer/looking-for.html",
      "https://google.github.io/eng-practices/review/developer/small-cls.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "apache",
     "name": "Apache Software Foundation",
     "sources": [
      "https://flink.apache.org/how-to-contribute/code-style-and-quality-pull-requests/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://devguide.python.org/getting-started/pull-request-lifecycle/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "llvm",
     "name": "LLVM",
     "sources": [
      "https://llvm.org/docs/Contributing.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "warne",
     "name": "Henrik Warne",
     "sources": [
      "https://henrikwarne.com/2024/01/10/tidy-first/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "kent-beck",
     "name": "Kent Beck",
     "sources": [
      "https://newsletter.kentbeck.com/p/getting-untangled"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/change#chg-refactor-separate",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-reuse-before-write",
   "level": "change",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "reuse",
    "duplication"
   ],
   "lang": "core",
   "applies_when": "The change adds a helper, util, type or constant.",
   "context": [],
   "tool_gate": "Clone-detection in CI (jscpd, PMD CPD, or similar) can flag near-duplicate blocks.",
   "note": "GitHub (reviewer repo search for each new helper), Cursor (explore before changing, since the agent may create a utility that exists) and Google's Gemini CLI team (review-duplication skill) back the search; the kernel style guide backs only \"use the existing helper\"; Ousterhout and refactoring.guru back the Why; \"author names where they searched\" had no source, so it left the Rule",
   "lacks": null,
   "rule": "Do not add a helper that duplicates one already in the repo; search for it first.",
   "why": "Reviewers see only the diff, so a reimplementation of a helper a few files away looks fine in isolation. Two copies then drift and get fixed once.",
   "counterpoint": "\"Searching costs more than writing ten lines.\" For tiny code that is true. Then the duplicate is tolerable and `mod-yagni` governs extraction. The cost flips once the logic holds a rule (parsing, money, auth) that must stay in one place.",
   "fail": {
    "language": "python",
    "code": "# utils/dates.py already has parse_iso(s)\ndef _parse_date(s):  # new, in billing/invoice.py\n    return datetime.strptime(s, \"%Y-%m-%d\")   # drops timezone handling"
   },
   "pass": {
    "language": "python",
    "code": "from utils.dates import parse_iso"
   },
   "sources": [
    "https://github.blog/ai-and-ml/generative-ai/agent-pull-requests-are-everywhere-heres-how-to-review-them/",
    "https://cursor.com/learn/understanding-your-codebase",
    "https://github.com/google-gemini/gemini-cli/blob/main/.gemini/skills/review-duplication/SKILL.md",
    "https://www.kernel.org/doc/html/latest/process/coding-style.html",
    "https://dev.to/sportebois/software-design-red-flags-wisdom-nuggets-from-john-ousterhout-43i2",
    "https://refactoring.guru/smells/duplicate-code"
   ],
   "orgs": [
    {
     "id": "github",
     "name": "GitHub",
     "sources": [
      "https://github.blog/ai-and-ml/generative-ai/agent-pull-requests-are-everywhere-heres-how-to-review-them/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "cursor",
     "name": "Cursor",
     "sources": [
      "https://cursor.com/learn/understanding-your-codebase"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://github.com/google-gemini/gemini-cli/blob/main/.gemini/skills/review-duplication/SKILL.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "kernel",
     "name": "Linux kernel",
     "sources": [
      "https://www.kernel.org/doc/html/latest/process/coding-style.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "refactoring-guru",
     "name": "Refactoring.Guru",
     "sources": [
      "https://refactoring.guru/smells/duplicate-code"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "portebois",
     "name": "Sébastien Portebois",
     "sources": [
      "https://dev.to/sportebois/software-design-red-flags-wisdom-nuggets-from-john-ousterhout-43i2"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/change#chg-reuse-before-write",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-review-signal",
   "level": "change",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "process"
   ],
   "topics": [
    "reviewer",
    "etiquette",
    "signal"
   ],
   "lang": "core",
   "applies_when": "A reviewer is deciding what to comment on.",
   "context": [],
   "tool_gate": "Formatter, linter and type checker own style and mechanical rules.",
   "note": "Microsoft says linters absorb part of review so humans focus on correctness and design. Anthropic (a vendor) backs only the tool-owned clause: its reviewer filters out findings that are only style, diagnostics or cleanup; not a counted third org and not the taste clause",
   "lacks": null,
   "rule": "Do not spend review comments on what a tool enforces or on minor style points; spend them on the findings that matter.",
   "why": "Noise buries the real defects and trains authors to dismiss review. Prefer a few high-conviction comments, and when a first pass turns up many, talk to the author instead of posting a long list; both are common advice but have fewer than three organisations behind them, and a style point outside the written guide is the reviewer's taste. A rubber-stamp approval is just as harmful: an approval must reflect real reading.",
   "counterpoint": "Limiting comments risks missing issues. The cap applies to opinion, not to defects, so every real defect is still raised. The written style guide or the formatter settles style, and repeated nits mean a missing gate.",
   "fail": {
    "language": "text",
    "code": "27 comments: 18 whitespace and import-order, 6 \"I'd write it differently\",\nthe null dereference on line 90 never mentioned."
   },
   "pass": {
    "language": "text",
    "code": "3 comments: the null dereference, a missing test for the empty case,\none nit. Formatting left to the formatter, which CI enforces."
   },
   "sources": [
    "https://google.github.io/eng-practices/review/reviewer/standard.html",
    "https://slack.engineering/how-about-code-reviews/",
    "https://blog.pragmaticengineer.com/good-code-reviews-better-code-reviews/",
    "https://microsoft.github.io/code-with-engineering-playbook/code-reviews/process-guidance/reviewer-guidance/",
    "https://engineering.fb.com/2022/11/16/culture/meta-code-review-time-improving/",
    "https://x.com/addyosmani/status/2114604912520921136"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://google.github.io/eng-practices/review/reviewer/standard.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "slack",
     "name": "Slack",
     "sources": [
      "https://slack.engineering/how-about-code-reviews/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "pragmatic-engineer",
     "name": "The Pragmatic Engineer",
     "sources": [
      "https://blog.pragmaticengineer.com/good-code-reviews-better-code-reviews/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://microsoft.github.io/code-with-engineering-playbook/code-reviews/process-guidance/reviewer-guidance/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "meta",
     "name": "Meta",
     "sources": [
      "https://engineering.fb.com/2022/11/16/culture/meta-code-review-time-improving/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "anthropic",
     "name": "Anthropic",
     "sources": [
      "https://x.com/addyosmani/status/2114604912520921136"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/change#chg-review-signal",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-rollback-path",
   "level": "change",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "reliability"
   ],
   "topics": [
    "release",
    "rollback"
   ],
   "lang": "core",
   "applies_when": "The change deploys schema, data, config or behaviour that is costly to undo.",
   "context": [],
   "tool_gate": "Migration linter requiring a down step; deploy pipeline with automatic abort.",
   "note": "thin sourcing; the programmingisterrible page returned 403 and is unverified: canarying with auto rollback only; migration rollback practice unsourced; Anthropic (a vendor) says to put \"Testing\", \"Risk\" and \"How to revert\" in the PR template",
   "lacks": null,
   "rule": "State in the description how to undo the change.",
   "why": "When a release goes wrong, the time to find the way back is before shipping. Irreversible steps (dropped data, sent messages) need an explicit decision. The author has checked that the route is possible. See [[inc-rollback-path-tested]] for drilling it and [[prod-canary-staged-rollout]] for abort rules.",
   "counterpoint": "\"We can always roll forward.\" Fine for stateless code, but not after data is destroyed or a client has cached the new contract. The check is cheap for reversible changes (one line) and bites only on irreversible ones.",
   "fail": {
    "language": "text",
    "code": "Migration drops column legacy_id and deletes backing rows.\nDescription: no rollback section. Code and migration deploy together."
   },
   "pass": {
    "language": "text",
    "code": "Rollback: revert PR; column kept for one release, dropped in a later PR.\nBehaviour behind a flag; canary at 5% with an abort rule."
   },
   "sources": [
    "https://x.com/addyosmani/status/2114604912520921136",
    "https://sre.google/workbook/canarying-releases/",
    "https://programmingisterrible.com/post/139222674273/write-code-that-is-easy-to-delete-not-easy-to",
    "https://handbook.gitlab.com/handbook/engineering/infrastructure-platforms/change-management/platform-leadership-review/",
    "https://learn.microsoft.com/en-us/compliance/assurance/assurance-microsoft-365-change-management",
    "https://docs.aws.amazon.com/wellarchitected/latest/framework/ops_mit_deploy_risks_plan_for_unsucessful_changes.html"
   ],
   "orgs": [
    {
     "id": "anthropic",
     "name": "Anthropic",
     "sources": [
      "https://x.com/addyosmani/status/2114604912520921136"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://sre.google/workbook/canarying-releases/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://handbook.gitlab.com/handbook/engineering/infrastructure-platforms/change-management/platform-leadership-review/"
     ],
     "strength": "supports"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/compliance/assurance/assurance-microsoft-365-change-management"
     ],
     "strength": "supports"
    },
    {
     "id": "aws",
     "name": "AWS",
     "sources": [
      "https://docs.aws.amazon.com/wellarchitected/latest/framework/ops_mit_deploy_risks_plan_for_unsucessful_changes.html"
     ],
     "strength": "supports"
    }
   ],
   "people": [
    {
     "id": "tef",
     "name": "tef (programmingisterrible)",
     "sources": [
      "https://programmingisterrible.com/post/139222674273/write-code-that-is-easy-to-delete-not-easy-to"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "strong",
   "url": "https://goodcodereview.com/change#chg-rollback-path",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-size-ceiling",
   "level": "change",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "process"
   ],
   "topics": [
    "size",
    "review-quality"
   ],
   "lang": "core",
   "applies_when": "The diff exceeds roughly 400 changed lines or about 20 files and is not generated or a pure move.",
   "context": [],
   "tool_gate": "Size labels or a diff-size warning in CI.",
   "note": "line counts come from VA.gov (400 to 500), USGS (250), Apache Quickstep (500) and a SmartBear write-up of a Cisco study (a vendor); original report not read. Godot, VA.gov and go-ethereum ask for a fuller description of a large change; Google's contributing guide asks for the reason",
   "lacks": null,
   "rule": "Split a change past about 400 changed lines or about 20 files, or say in the description why not; aim for about 200.",
   "why": "Review usefulness falls as changes grow, so big diffs get skimmed and approved on trust.",
   "counterpoint": "\"Line counts are arbitrary.\" The exact numbers are rough, but the direction is not: larger changes get fewer useful comments per file and are approved on trust. Treat 400 as a prompt to split or explain, not a hard gate, and exclude generated code and pure moves.",
   "fail": {
    "language": "text",
    "code": "1,800-line PR, 55 files, one approval after 10 minutes, no split plan."
   },
   "pass": {
    "language": "text",
    "code": "Stack of 6 changes of 150-300 lines, each builds and passes tests alone."
   },
   "sources": [
    "https://sback.it/publications/icse2018seip.pdf",
    "https://www.microsoft.com/en-us/research/publication/characteristics-of-useful-code-reviews-an-empirical-study-at-microsoft/",
    "https://smartbear.com/learn/code-review/best-practices-for-peer-code-review/",
    "https://newsletter.pragmaticengineer.com/p/stacked-diffs",
    "https://depo-platform-documentation.scrollhelp.site/developer-docs/writing-small-prs",
    "https://astrogeology.usgs.gov/docs/how-to-guides/software-management/guidelines-for-pull-requests/",
    "https://cwiki.apache.org/confluence/display/QUICKSTEP/Code+Review+Guidelines",
    "https://contributing.godotengine.org/en/latest/development/contribution_rules.html",
    "https://geth.ethereum.org/docs/developers/geth-developer/code-review-guidelines",
    "https://github.com/google/langextract/blob/main/CONTRIBUTING.md"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://sback.it/publications/icse2018seip.pdf",
      "https://github.com/google/langextract/blob/main/CONTRIBUTING.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://www.microsoft.com/en-us/research/publication/characteristics-of-useful-code-reviews-an-empirical-study-at-microsoft/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "smartbear",
     "name": "SmartBear",
     "sources": [
      "https://smartbear.com/learn/code-review/best-practices-for-peer-code-review/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "pragmatic-engineer",
     "name": "The Pragmatic Engineer",
     "sources": [
      "https://newsletter.pragmaticengineer.com/p/stacked-diffs"
     ],
     "strength": "unjudged"
    },
    {
     "id": "va",
     "name": "US Department of Veterans Affairs",
     "sources": [
      "https://depo-platform-documentation.scrollhelp.site/developer-docs/writing-small-prs"
     ],
     "strength": "unjudged"
    },
    {
     "id": "usgs",
     "name": "USGS",
     "sources": [
      "https://astrogeology.usgs.gov/docs/how-to-guides/software-management/guidelines-for-pull-requests/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "apache",
     "name": "Apache Software Foundation",
     "sources": [
      "https://cwiki.apache.org/confluence/display/QUICKSTEP/Code+Review+Guidelines"
     ],
     "strength": "unjudged"
    },
    {
     "id": "godot",
     "name": "Godot Engine",
     "sources": [
      "https://contributing.godotengine.org/en/latest/development/contribution_rules.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "ethereum",
     "name": "Ethereum Foundation",
     "sources": [
      "https://geth.ethereum.org/docs/developers/geth-developer/code-review-guidelines"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/change#chg-size-ceiling",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-tests-ship-with-change",
   "level": "change",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "testing"
   ],
   "topics": [],
   "lang": "core",
   "applies_when": "The change alters behaviour of non-trivial code.",
   "context": [],
   "tool_gate": "Coverage-delta or \"changed source without changed tests\" check in CI.",
   "note": "Rust and Rails state the test that fails without the change; Microsoft and Google state the same-change arm. Only Mozilla (the reviewer writes the reason) and GitLab (open an issue for the gap) touch the stated-reason arm, so it sits in the Counterpoint, not the Rule",
   "lacks": null,
   "rule": "Ship new or changed behaviour in the same change as a test that fails if the behaviour breaks.",
   "why": "\"Tests later\" mostly means never, and the reviewer cannot tell from reading whether the code does what the author intended.",
   "counterpoint": "\"Holding a good change for tests blocks progress.\" Approve changes that improve the code even when imperfect, but the test is part of the change, not polish. Untestable cases exist (config, pure docs, emergency fixes), and a written reason plus a ticket for the test is the usual way to ship without one.",
   "fail": {
    "language": "text",
    "code": "PR touches billing/discount.py (+90 lines). No file under tests/ changed.\nDescription: \"will add tests in a follow-up\"."
   },
   "pass": {
    "language": "text",
    "code": "PR adds discount.py change and test_discount.py with the edge cases\n(zero quantity, stacked coupons); the test was seen failing before the fix."
   },
   "sources": [
    "https://www.kubernetes.dev/docs/guide/pull-requests/",
    "https://devguide.python.org/getting-started/pull-request-lifecycle/",
    "https://google.github.io/eng-practices/review/reviewer/looking-for.html",
    "https://rustc-dev-guide.rust-lang.org/tests/adding.html",
    "https://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html",
    "https://microsoft.github.io/code-with-engineering-playbook/code-reviews/process-guidance/reviewer-guidance/",
    "https://firefox-source-docs.mozilla.org/testing/testing-policy/index.html"
   ],
   "orgs": [
    {
     "id": "kubernetes",
     "name": "Kubernetes",
     "sources": [
      "https://www.kubernetes.dev/docs/guide/pull-requests/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://devguide.python.org/getting-started/pull-request-lifecycle/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://google.github.io/eng-practices/review/reviewer/looking-for.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "rust",
     "name": "Rust project",
     "sources": [
      "https://rustc-dev-guide.rust-lang.org/tests/adding.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "rails",
     "name": "Ruby on Rails",
     "sources": [
      "https://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://microsoft.github.io/code-with-engineering-playbook/code-reviews/process-guidance/reviewer-guidance/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://firefox-source-docs.mozilla.org/testing/testing-policy/index.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/change#chg-tests-ship-with-change",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-ui-change-screenshots-and-keyboard-pass",
   "level": "change",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "accessibility",
    "usability",
    "process"
   ],
   "topics": [
    "frontend"
   ],
   "lang": "core",
   "applies_when": "The change alters markup, styles, or behaviour a user sees or operates.",
   "context": [
    "ui"
   ],
   "tool_gate": "A pull request template with a UI checklist; the screenshots and the keyboard result are the evidence.",
   "note": "running a keyboard pass is backed by W3C, WebAIM, GOV.UK, but reporting it in the change only by the WordPress and Elastic PR templates (2); 200% zoom is W3C only; screenshots rest on three PR templates (WordPress, Elastic, Mozilla), and WordPress's section is optional",
   "lacks": null,
   "rule": "A UI change shows before-and-after screenshots and says what a keyboard-only pass and a 200% zoom check found on the changed screens.",
   "why": "A reviewer reading a diff cannot see focus, order, or overflow. A screenshot shows the visual change, and the keyboard pass shows what the diff hides: a lost focus ring, a trap, a control that cannot be reached.",
   "counterpoint": "\"Automated checks run in CI.\" They find a minority of problems. They cannot tell that Tab order is wrong or that focus vanished. Say what you did not test, as the last line shows.",
   "fail": {
    "language": "text",
    "code": "PR: \"Redesign the settings dialog\"\nFiles: Settings.tsx, settings.css\n(no screenshots, no mention of keyboard or zoom)"
   },
   "pass": {
    "language": "text",
    "code": "PR: \"Redesign the settings dialog\"\nBefore / after: screenshots attached (default and 200% zoom).\nKeyboard: Tab reaches every control in order, Esc closes, focus returns to the gear button.\nNot covered: screen reader, not tested."
   },
   "sources": [
    "https://www.w3.org/WAI/test-evaluate/preliminary/",
    "https://webaim.org/techniques/keyboard/",
    "https://www.gov.uk/service-manual/helping-people-to-use-your-service/making-your-service-accessible-an-introduction",
    "https://raw.githubusercontent.com/WordPress/gutenberg/trunk/.github/PULL_REQUEST_TEMPLATE.md",
    "https://raw.githubusercontent.com/elastic/eui/main/.github/pull_request_template.md",
    "https://raw.githubusercontent.com/mozilla/addons-frontend/master/.github/PULL_REQUEST_TEMPLATE.md"
   ],
   "orgs": [
    {
     "id": "w3c",
     "name": "W3C",
     "sources": [
      "https://www.w3.org/WAI/test-evaluate/preliminary/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "webaim",
     "name": "WebAIM",
     "sources": [
      "https://webaim.org/techniques/keyboard/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "govuk",
     "name": "GOV.UK",
     "sources": [
      "https://www.gov.uk/service-manual/helping-people-to-use-your-service/making-your-service-accessible-an-introduction"
     ],
     "strength": "unjudged"
    },
    {
     "id": "wordpress",
     "name": "WordPress",
     "sources": [
      "https://raw.githubusercontent.com/WordPress/gutenberg/trunk/.github/PULL_REQUEST_TEMPLATE.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "elastic",
     "name": "Elastic",
     "sources": [
      "https://raw.githubusercontent.com/elastic/eui/main/.github/pull_request_template.md"
     ],
     "strength": "unjudged"
    },
    {
     "id": "mozilla",
     "name": "Mozilla",
     "sources": [
      "https://raw.githubusercontent.com/mozilla/addons-frontend/master/.github/PULL_REQUEST_TEMPLATE.md"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/change#chg-ui-change-screenshots-and-keyboard-pass",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "chg-verify-before-claim",
   "level": "change",
   "status": "stable",
   "severity": "block",
   "concerns": [
    "testing",
    "process"
   ],
   "topics": [
    "verification",
    "reviewer"
   ],
   "lang": "core",
   "applies_when": "The author or reviewer asserts the change works or a finding is real.",
   "context": [],
   "tool_gate": null,
   "note": "Python, Google and GitLab back the run-something clause; GitLab asks for maintainer review only when tests pass and for an explanation when they fail. Anthropic (a vendor) also backs run-it-and-show-the-result, \"Instead of saying the task is done, we ask it to show us the result\", and is not counted",
   "lacks": null,
   "rule": "Back every \"works\" or \"bug\" claim with something run: a repro, a test or command output.",
   "why": "Reasoning that \"looks correct\" misses stateful and corner cases, and a reviewer's false finding wastes the author's time. For a bug fix, reproduce the bug first and rerun the repro after the change.",
   "counterpoint": "Running everything is slow. Scale with risk: a rename needs no repro, but a race, a bug fix, or a UI change does. See it run, do not only read it.",
   "fail": {
    "language": "text",
    "code": "Author: \"Fixed the race, trust me.\" Reviewer: \"LGTM, reads fine.\"\nNobody ran the failing case."
   },
   "pass": {
    "language": "text",
    "code": "Author: \"repro: script X failed 3/10 runs; after fix 0/200. Test added.\"\nReviewer checked out the branch and reran it."
   },
   "sources": [
    "https://devguide.python.org/getting-started/pull-request-lifecycle/",
    "https://secure.phabricator.com/book/phabflavor/article/writing_reviewable_code/",
    "https://danluu.com/tests-v-reason/",
    "https://google.github.io/eng-practices/review/reviewer/looking-for.html",
    "https://x.com/addyosmani/status/2114604912520921136",
    "https://docs.gitlab.com/development/code_review/"
   ],
   "orgs": [
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://devguide.python.org/getting-started/pull-request-lifecycle/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "phacility",
     "name": "Phacility",
     "sources": [
      "https://secure.phabricator.com/book/phabflavor/article/writing_reviewable_code/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://google.github.io/eng-practices/review/reviewer/looking-for.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "anthropic",
     "name": "Anthropic",
     "sources": [
      "https://x.com/addyosmani/status/2114604912520921136"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://docs.gitlab.com/development/code_review/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "dan-luu",
     "name": "Dan Luu",
     "sources": [
      "https://danluu.com/tests-v-reason/"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/change#chg-verify-before-claim",
   "markdown_url": "https://goodcodereview.com/change.md"
  },
  {
   "id": "py-cancellation",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "reliability",
    "correctness"
   ],
   "topics": [
    "asyncio",
    "cancellation"
   ],
   "lang": "python",
   "applies_when": "Async code catches exceptions around awaits or uses asyncio.timeout.",
   "context": [],
   "tool_gate": "Ruff BLE001 / S110 for broad swallow; lint for CancelledError needs custom rule.",
   "note": null,
   "lacks": null,
   "rule": "Re-raise `CancelledError` (do not catch it via bare `except` or `BaseException`), and catch `TimeoutError` outside the `asyncio.timeout()` block.",
   "why": "TaskGroup and timeouts are built on cancellation, so swallowing it makes shutdown hang and timeouts silently not fire. The conversion to `TimeoutError` happens at context exit, so a handler inside the block never sees it.",
   "counterpoint": "\"Cleanup code must run even on cancel.\" Clean up in `finally`, then re-raise, and never suppress. `uncancel()` is task bookkeeping, not permission to swallow.",
   "fail": {
    "language": "python",
    "code": "async with asyncio.timeout(5):\n    try: await fetch()\n    except TimeoutError: ...        # never reached\n    except BaseException: pass      # eats cancellation"
   },
   "pass": {
    "language": "python",
    "code": "try:\n    async with asyncio.timeout(5):\n        await fetch()\nexcept TimeoutError:\n    handle_timeout()"
   },
   "sources": [
    "https://docs.python.org/3/library/asyncio-task.html",
    "https://pablogonzalez.me/posts/python-code-review-guide/"
   ],
   "orgs": [
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://docs.python.org/3/library/asyncio-task.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "pablo-gonzalez",
     "name": "Pablo González",
     "sources": [
      "https://pablogonzalez.me/posts/python-code-review-guide/"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/pack-python#py-cancellation",
   "markdown_url": "https://goodcodereview.com/pack-python.md"
  },
  {
   "id": "py-class-earns-its-instance",
   "level": "module",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "python",
    "classes"
   ],
   "lang": "python",
   "applies_when": "A Python class has no state, only static methods, or a `Protocol` with one implementation.",
   "context": [],
   "tool_gate": "ruff PLR6301 (partial), Pylint R0903 (min-public-methods=2, dataclasses exempt; it counts public methods, not state, so a static-only class with two methods passes)",
   "note": "Go review guide and the creator's Python guide back the rule; Refactoring.Guru and Pylint add the class half. Pylint's threshold is 2 and it exempts dataclasses, so treat it as a prompt, not a hard gate",
   "lacks": null,
   "rule": "Give a class real state or a collaborator; otherwise write a module function.",
   "why": "A static-only class is a module with extra typing. A `Protocol` with one implementation is a speculative interface. A `Protocol` needs two or more real implementations swapped at runtime; see [[mod-yagni]]. Every class costs time to understand and maintain, so one that does too little should become a function. A class kept to mark future work is the same speculation: delete it, or name the plan and its owner. Pylint flags a class with too few public methods and shows a dataclass plus a function, or a larger API, as the fixes.",
   "counterpoint": "\"Protocols make testing easy.\" A fake of a concrete class in tests works with duck typing too. Declare an interface where it is consumed, and avoid speculative ones.",
   "fail": {
    "language": "python",
    "code": "class DateUtils:\n    @staticmethod\n    def parse(s): ...\nclass TaxCalc:\n    def total(self, order): return order.total   # no state, one method\nclass Notifier(Protocol): ...   # one impl, ever"
   },
   "pass": {
    "language": "text",
    "code": "dates.py:  def parse(s): ...\ntax.py:    def total(order): ...\n# use the concrete class until a second real impl exists"
   },
   "sources": [
    "https://go.dev/wiki/CodeReviewComments",
    "https://pablogonzalez.me/posts/python-code-review-guide/",
    "https://refactoring.guru/smells/lazy-class",
    "https://pylint.readthedocs.io/en/latest/user_guide/messages/refactor/too-few-public-methods.html"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://go.dev/wiki/CodeReviewComments"
     ],
     "strength": "unjudged"
    },
    {
     "id": "refactoring-guru",
     "name": "Refactoring.Guru",
     "sources": [
      "https://refactoring.guru/smells/lazy-class"
     ],
     "strength": "unjudged"
    },
    {
     "id": "pylint",
     "name": "Pylint",
     "sources": [
      "https://pylint.readthedocs.io/en/latest/user_guide/messages/refactor/too-few-public-methods.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "pablo-gonzalez",
     "name": "Pablo González",
     "sources": [
      "https://pablogonzalez.me/posts/python-code-review-guide/"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/pack-python#py-class-earns-its-instance",
   "markdown_url": "https://goodcodereview.com/pack-python.md"
  },
  {
   "id": "py-docstrings-google",
   "level": "function",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "python",
    "docs"
   ],
   "lang": "python",
   "applies_when": "A Python function or class has, or lacks, a docstring.",
   "context": [],
   "tool_gate": "ruff D (convention=google, D1xx ignored)",
   "note": null,
   "lacks": null,
   "rule": "Write a Google-style docstring only when it adds what the signature cannot say.",
   "why": "Forced docstrings restate the signature and rot. The ones that matter state units, side effects, and what callers may rely on. The gate never forces presence; it checks format only.",
   "counterpoint": "\"Every public function needs a docstring.\" Ousterhout would sympathise (missing docs cost more than wrong ones), and Martin would not. The compromise here is to require content that adds information, not presence.",
   "fail": {
    "language": "python",
    "code": "def add(a, b):\n    \"\"\"Add a and b. :param a: first number\"\"\""
   },
   "pass": {
    "language": "python",
    "code": "def charge(order):\n    \"\"\"Charge the card once; safe to retry with the same order id (idempotent).\"\"\""
   },
   "sources": [
    "https://google.github.io/eng-practices/review/reviewer/looking-for.html",
    "https://pablogonzalez.me/posts/python-code-review-guide/"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://google.github.io/eng-practices/review/reviewer/looking-for.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "pablo-gonzalez",
     "name": "Pablo González",
     "sources": [
      "https://pablogonzalez.me/posts/python-code-review-guide/"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/pack-python#py-docstrings-google",
   "markdown_url": "https://goodcodereview.com/pack-python.md"
  },
  {
   "id": "py-field-only-when-adds",
   "level": "module",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "correctness"
   ],
   "topics": [
    "python",
    "pydantic"
   ],
   "lang": "python",
   "applies_when": "A pydantic model or settings class uses `Field()`.",
   "context": [],
   "tool_gate": null,
   "note": "from the author's own Python review guide",
   "lacks": null,
   "rule": "Use `Field()` only when it adds something (a constraint, an alias, or API docs), and set plain defaults directly.",
   "why": "`Field(default=5.0)` is noise that hides which fields are constrained.",
   "counterpoint": "\"Uniform `Field` everywhere is consistent.\" Consistency that hides the constraints is not worth the scan cost. Every `Field` should tell the reader something.",
   "fail": {
    "language": "python",
    "code": "request_timeout_s: float = Field(default=5.0)"
   },
   "pass": {
    "language": "python",
    "code": "request_timeout_s: float = 5.0\nmax_batch_size: int = Field(default=500, gt=0, le=2_000)"
   },
   "sources": [
    "https://pablogonzalez.me/posts/python-code-review-guide/"
   ],
   "orgs": [],
   "people": [
    {
     "id": "pablo-gonzalez",
     "name": "Pablo González",
     "sources": [
      "https://pablogonzalez.me/posts/python-code-review-guide/"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/pack-python#py-field-only-when-adds",
   "markdown_url": "https://goodcodereview.com/pack-python.md"
  },
  {
   "id": "py-httpx-client-lifespan",
   "level": "module",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "performance"
   ],
   "topics": [
    "python",
    "resources"
   ],
   "lang": "python",
   "applies_when": "A Python service creates an `httpx` client.",
   "context": [],
   "tool_gate": null,
   "note": "from the author's own Python review guide",
   "lacks": null,
   "rule": "Pass an explicit `httpx.Timeout` to every `httpx` client.",
   "why": "Core rules: [[svc-timeouts]], [[mod-pooled-clients-built-once]]. `read=None` disables the read timeout. Build the client once in the app lifespan and reuse it.",
   "counterpoint": "\"httpx defaults are fine.\" They are (5s), but set the value explicitly so that a library change cannot silently change behaviour.",
   "fail": {
    "language": "python",
    "code": "async def fetch(url):\n    async with httpx.AsyncClient(timeout=None) as c:\n        return await c.get(url)"
   },
   "pass": {
    "language": "python",
    "code": "@asynccontextmanager\nasync def lifespan(app):\n    async with httpx.AsyncClient(timeout=httpx.Timeout(10.0, connect=2.0)) as c:\n        app.state.http = c\n        yield"
   },
   "sources": [
    "https://pablogonzalez.me/posts/python-code-review-guide/"
   ],
   "orgs": [],
   "people": [
    {
     "id": "pablo-gonzalez",
     "name": "Pablo González",
     "sources": [
      "https://pablogonzalez.me/posts/python-code-review-guide/"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/pack-python#py-httpx-client-lifespan",
   "markdown_url": "https://goodcodereview.com/pack-python.md"
  },
  {
   "id": "py-imports",
   "level": "module",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "python",
    "imports"
   ],
   "lang": "python",
   "applies_when": "A Python module has imports.",
   "context": [],
   "tool_gate": "ruff I, TID252, PLC0415",
   "note": "from the author's own Python review guide",
   "lacks": null,
   "rule": "Put imports at module top, absolute only.",
   "why": "Local imports hide dependencies and cycles. Relative imports break when files move. Importing a private name couples you to something its owner may change without notice. Never import a `_`-prefixed name from another module, and use `__all__` only in a re-export `__init__.py`.",
   "counterpoint": "\"Import inside the function avoids a cycle.\" It hides the cycle instead of fixing it, and the design needs a third module or a dependency inversion. A lazy import for a heavy optional dependency is the one defensible exception; document it with a reason.",
   "fail": {
    "language": "python",
    "code": "def handler():\n    from .helpers import _parse"
   },
   "pass": {
    "language": "python",
    "code": "from app.billing.parsing import parse"
   },
   "sources": [
    "https://pablogonzalez.me/posts/python-code-review-guide/"
   ],
   "orgs": [],
   "people": [
    {
     "id": "pablo-gonzalez",
     "name": "Pablo González",
     "sources": [
      "https://pablogonzalez.me/posts/python-code-review-guide/"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/pack-python#py-imports",
   "markdown_url": "https://goodcodereview.com/pack-python.md"
  },
  {
   "id": "py-modern-typing",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "correctness"
   ],
   "topics": [
    "python",
    "types"
   ],
   "lang": "python",
   "applies_when": "Annotations use `typing` aliases such as `Optional` or `Union`.",
   "context": [],
   "tool_gate": "ruff UP; mypy --strict; pyright",
   "note": "from the author's own Python review guide",
   "lacks": null,
   "rule": "Write `X | None` and builtin generics, not `typing` aliases.",
   "why": "A tool decides these, so a human comment is waste. Check that the gate exists, and gate new code with `mypy --strict` and pyright.",
   "counterpoint": "\"Two type checkers is overkill.\" They disagree on edge cases, so running both on new code catches different mistakes. Pick at least one as a hard gate.",
   "fail": {
    "language": "python",
    "code": "from typing import Optional, List\ndef f(x: Optional[List[int]]): ..."
   },
   "pass": {
    "language": "python",
    "code": "def f(x: list[int] | None) -> None: ..."
   },
   "sources": [
    "https://pablogonzalez.me/posts/python-code-review-guide/"
   ],
   "orgs": [],
   "people": [
    {
     "id": "pablo-gonzalez",
     "name": "Pablo González",
     "sources": [
      "https://pablogonzalez.me/posts/python-code-review-guide/"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/pack-python#py-modern-typing",
   "markdown_url": "https://goodcodereview.com/pack-python.md"
  },
  {
   "id": "py-no-cast",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "correctness"
   ],
   "topics": [
    "python",
    "types"
   ],
   "lang": "python",
   "applies_when": "Code calls `typing.cast(...)`.",
   "context": [],
   "tool_gate": "ruff TID251 (banned-api)",
   "note": "from the author's own Python review guide",
   "lacks": null,
   "rule": "Do not use `cast()`; parse the boundary into a typed model, or narrow with `isinstance`.",
   "why": "`cast` is a promise to the type checker that nothing verifies at runtime. When the promise is wrong, the type checker's output is a lie that spreads.",
   "counterpoint": "\"Sometimes the checker is wrong.\" Then fix the stub or narrow with `isinstance` or `assert`, which check at runtime. A justified `type: ignore[code]` with a reason is more honest than a cast, because it stays visible.",
   "fail": {
    "language": "python",
    "code": "data = cast(dict[str, int], json.loads(raw))"
   },
   "pass": {
    "language": "python",
    "code": "class Payload(BaseModel):\n    counts: dict[str, int]\ndata = Payload.model_validate_json(raw)"
   },
   "sources": [
    "https://pablogonzalez.me/posts/python-code-review-guide/"
   ],
   "orgs": [],
   "people": [
    {
     "id": "pablo-gonzalez",
     "name": "Pablo González",
     "sources": [
      "https://pablogonzalez.me/posts/python-code-review-guide/"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/pack-python#py-no-cast",
   "markdown_url": "https://goodcodereview.com/pack-python.md"
  },
  {
   "id": "py-no-init-or-model-tests",
   "level": "module",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "testing"
   ],
   "topics": [
    "python"
   ],
   "lang": "python",
   "applies_when": "The code adds `test_init_*` or a test file for a plain model or dataclass.",
   "context": [],
   "tool_gate": null,
   "note": "from the author's own Python review guide",
   "lacks": null,
   "rule": "Cover `__init__` and plain models through the behaviour that uses them.",
   "why": "Such tests restate the class and break on every field change without proving behaviour.",
   "counterpoint": "\"Validators on the model need tests.\" Yes: test the validator's behaviour (valid and invalid input), not that assignment works.",
   "fail": {
    "language": "python",
    "code": "def test_init_sets_name(): assert User(name=\"a\").name == \"a\""
   },
   "pass": {
    "language": "python",
    "code": "def test_welcome_email_uses_user_name(): ..."
   },
   "sources": [
    "https://pablogonzalez.me/posts/python-code-review-guide/"
   ],
   "orgs": [],
   "people": [
    {
     "id": "pablo-gonzalez",
     "name": "Pablo González",
     "sources": [
      "https://pablogonzalez.me/posts/python-code-review-guide/"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/pack-python#py-no-init-or-model-tests",
   "markdown_url": "https://goodcodereview.com/pack-python.md"
  },
  {
   "id": "py-paths-from-file",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "correctness"
   ],
   "topics": [
    "python",
    "io"
   ],
   "lang": "python",
   "applies_when": "Python code builds a path to a data file or fixture.",
   "context": [],
   "tool_gate": null,
   "note": "from the author's own Python review guide",
   "lacks": null,
   "rule": "Resolve paths from `__file__`, not the current working directory.",
   "why": "A cwd-relative path works from the repo root and fails from CI, an IDE, or an installed package.",
   "counterpoint": "\"Tests always run from the root.\" That holds until someone runs one test from its directory, or an installer moves it.",
   "fail": {
    "language": "python",
    "code": "Path(\"tests/fixtures/data.json\")"
   },
   "pass": {
    "language": "python",
    "code": "Path(__file__).parent / \"fixtures\" / \"data.json\""
   },
   "sources": [
    "https://pablogonzalez.me/posts/python-code-review-guide/"
   ],
   "orgs": [],
   "people": [
    {
     "id": "pablo-gonzalez",
     "name": "Pablo González",
     "sources": [
      "https://pablogonzalez.me/posts/python-code-review-guide/"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/pack-python#py-paths-from-file",
   "markdown_url": "https://goodcodereview.com/pack-python.md"
  },
  {
   "id": "py-pytest-expensive-fixture-scope",
   "level": "module",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "testing",
    "performance"
   ],
   "topics": [
    "python",
    "fixtures"
   ],
   "lang": "python",
   "applies_when": "A pytest or unittest suite builds a database, a container, a client or a model in a fixture that many tests use.",
   "context": [],
   "tool_gate": "pytest --durations=10 --durations-min=1.0 to find the fixture, and a CI job timeout to hold the line.",
   "note": "scope - pytest (module-scoped smtp_connection runs \"as quick as a single one\"; dynamic scope for slow setups like docker containers), Python (setUpClass for an expensive connection); cached database plus rollback and --reuse-db - pytest-django; the SQLAlchemy recipe for a rolled-back Session - SQLAlchemy",
   "lacks": null,
   "rule": "Give an expensive pytest fixture `scope=\"session\"` or `\"module\"` and isolate each test with a rollback, never a schema build or a seed load inside a function-scoped fixture.",
   "why": "pytest destroys a function-scoped fixture after every test, and function scope is the default, so an expensive fixture pays its full cost for each test. A module or session scope pays it once. unittest has the same trade: `setUpClass` builds once per class, `setUp` once per test.",
   "counterpoint": "\"A session fixture is shared, so one test can leak state into the next.\" That holds for mutable objects, which is why the PASS keeps the engine shared and gives each test its own rolled-back session. A fixture that cannot be reset stays at function scope. Python's own docs warn that shared fixtures break isolation and parallelism, so build the shared part read-only.",
   "fail": {
    "language": "python",
    "code": "@pytest.fixture                       # scope defaults to \"function\"\ndef db():\n    engine = create_engine(TEST_URL)\n    Base.metadata.create_all(engine)  # ~25 s, repeated for every test\n    seed_reference_data(engine)\n    yield engine\n    Base.metadata.drop_all(engine)"
   },
   "pass": {
    "language": "python",
    "code": "@pytest.fixture(scope=\"session\")\ndef engine():\n    engine = create_engine(TEST_URL)\n    Base.metadata.create_all(engine)  # ~25 s, once\n    seed_reference_data(engine)\n    yield engine\n    engine.dispose()\n\n@pytest.fixture\ndef db(engine):\n    conn = engine.connect()\n    tx = conn.begin()\n    session = Session(bind=conn, join_transaction_mode=\"create_savepoint\")\n    yield session                     # milliseconds per test\n    session.close()\n    tx.rollback()\n    conn.close()"
   },
   "sources": [
    "https://docs.pytest.org/en/stable/how-to/fixtures.html",
    "https://docs.python.org/3/library/unittest.html",
    "https://pytest-django.readthedocs.io/en/latest/database.html",
    "https://docs.sqlalchemy.org/en/20/orm/session_transaction.html#joining-a-session-into-an-external-transaction-such-as-for-test-suites",
    "https://docs.djangoproject.com/en/5.1/topics/testing/tools/",
    "https://github.com/zupo/awesome-pytest-speedup"
   ],
   "orgs": [
    {
     "id": "pytest",
     "name": "pytest",
     "sources": [
      "https://docs.pytest.org/en/stable/how-to/fixtures.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://docs.python.org/3/library/unittest.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "pytest-django",
     "name": "pytest-django",
     "sources": [
      "https://pytest-django.readthedocs.io/en/latest/database.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "sqlalchemy",
     "name": "SQLAlchemy",
     "sources": [
      "https://docs.sqlalchemy.org/en/20/orm/session_transaction.html#joining-a-session-into-an-external-transaction-such-as-for-test-suites"
     ],
     "strength": "unjudged"
    },
    {
     "id": "django",
     "name": "Django Software Foundation",
     "sources": [
      "https://docs.djangoproject.com/en/5.1/topics/testing/tools/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "zupo",
     "name": "awesome-pytest-speedup (zupo)",
     "sources": [
      "https://github.com/zupo/awesome-pytest-speedup"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/pack-python#py-pytest-expensive-fixture-scope",
   "markdown_url": "https://goodcodereview.com/pack-python.md"
  },
  {
   "id": "py-pytest-parametrize",
   "level": "function",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "testing"
   ],
   "topics": [
    "python"
   ],
   "lang": "python",
   "applies_when": "Tests differ only in inputs and expected values.",
   "context": [],
   "tool_gate": "ruff PT006/PT007 (style only)",
   "note": "Go (copy-paste a test, use a table), Microsoft (parameterized tests) and Vodafone (three tests that are one test with different inputs) state the practice; pytest documents the tool",
   "lacks": null,
   "rule": "Write tests that differ only in inputs and expected values as one `@pytest.mark.parametrize` test.",
   "why": "Copy-pasted tests drift, and an empty parametrization silently runs nothing. Assert that a computed parameter list is not empty first.",
   "counterpoint": "\"Separate tests give clearer failures.\" Parametrize ids give one failure per case, with its inputs. For a computed list, `assert cases` before parametrising.",
   "fail": {
    "language": "python",
    "code": "def test_retry_502(): assert is_retryable(Response(502))\ndef test_retry_503(): assert is_retryable(Response(503))"
   },
   "pass": {
    "language": "python",
    "code": "@pytest.mark.parametrize((\"status\", \"retry\"), [(502, True), (503, True), (400, False)])\ndef test_retries_gateway_errors(status, retry):\n    assert is_retryable(httpx.Response(status)) is retry"
   },
   "sources": [
    "https://go.dev/wiki/TableDrivenTests",
    "https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices",
    "https://tech.gr.vodafone.com/post/junit-parameterized-tests",
    "https://docs.pytest.org/en/stable/how-to/parametrize.html",
    "https://pablogonzalez.me/posts/python-code-review-guide/"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://go.dev/wiki/TableDrivenTests"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices"
     ],
     "strength": "unjudged"
    },
    {
     "id": "vodafone",
     "name": "Vodafone",
     "sources": [
      "https://tech.gr.vodafone.com/post/junit-parameterized-tests"
     ],
     "strength": "unjudged"
    },
    {
     "id": "pytest",
     "name": "pytest",
     "sources": [
      "https://docs.pytest.org/en/stable/how-to/parametrize.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "pablo-gonzalez",
     "name": "Pablo González",
     "sources": [
      "https://pablogonzalez.me/posts/python-code-review-guide/"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/pack-python#py-pytest-parametrize",
   "markdown_url": "https://goodcodereview.com/pack-python.md"
  },
  {
   "id": "py-ruff-gate",
   "level": "codebase",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "maintainability"
   ],
   "topics": [
    "python",
    "tooling"
   ],
   "lang": "python",
   "applies_when": "The project is Python and has a linter configuration.",
   "context": [],
   "tool_gate": "ruff",
   "note": "from the author's own Python review guide",
   "lacks": null,
   "rule": "Enable the Ruff rule sets E, F, I, B, UP, SIM, BLE, TID and D in CI.",
   "why": "The tool owns formatting, import order, the choice between `Optional` and `X | None`, bare `except`, and relative imports. Human review time goes to what tools cannot see. `D1xx` stays ignored: the gate checks docstring format, not presence. Anything the gate decides is not a review comment; the comment is \"add the gate\".",
   "counterpoint": "\"Strict lint slows contributors.\" It speeds review: repeated nitpicks are a sign of a missing gate (Google, Pragmatic Engineer notes).",
   "fail": {
    "language": "toml",
    "code": "[tool.ruff.lint]\nselect = [\"E\", \"F\"]"
   },
   "pass": {
    "language": "toml",
    "code": "[tool.ruff.lint]\nselect = [\"E\", \"F\", \"I\", \"B\", \"UP\", \"SIM\", \"BLE\", \"TID\", \"D\"]\nignore = [\"D100\", \"D101\", \"D102\", \"D103\", \"D104\", \"D105\", \"D106\", \"D107\"]\n[tool.ruff.lint.pydocstyle]\nconvention = \"google\"\n[tool.ruff.lint.flake8-tidy-imports]\nban-relative-imports = \"all\""
   },
   "sources": [
    "https://pablogonzalez.me/posts/python-code-review-guide/"
   ],
   "orgs": [],
   "people": [
    {
     "id": "pablo-gonzalez",
     "name": "Pablo González",
     "sources": [
      "https://pablogonzalez.me/posts/python-code-review-guide/"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/pack-python#py-ruff-gate",
   "markdown_url": "https://goodcodereview.com/pack-python.md"
  },
  {
   "id": "py-task-reference",
   "level": "service",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "correctness"
   ],
   "topics": [
    "asyncio",
    "concurrency"
   ],
   "lang": "python",
   "applies_when": "Python code calls asyncio.create_task or ensure_future.",
   "context": [],
   "tool_gate": "Ruff RUF006 (asyncio-dangling-task).",
   "note": null,
   "lacks": null,
   "rule": "Never discard the result of `create_task`; keep a reference and surface its exceptions, preferably through a `TaskGroup`.",
   "why": "The event loop holds only weak references, so an unreferenced task can be garbage-collected mid-run. A task that nobody awaits also loses its exception (\"never retrieved\").",
   "counterpoint": "\"The set-and-discard recipe from the docs is enough.\" It keeps the task alive but still never retrieves the exception, and the docs point to TaskGroup for that. The same reference rule applies to tasks passed to `asyncio.shield`.",
   "fail": {
    "language": "python",
    "code": "asyncio.create_task(send_email(user))   # fire and forget"
   },
   "pass": {
    "language": "python",
    "code": "async with asyncio.TaskGroup() as tg:\n    tg.create_task(send_email(user))\n# or: t = create_task(...); bg.add(t); t.add_done_callback(bg.discard)"
   },
   "sources": [
    "https://docs.python.org/3/library/asyncio-task.html",
    "https://pablogonzalez.me/posts/python-code-review-guide/"
   ],
   "orgs": [
    {
     "id": "psf",
     "name": "Python Software Foundation",
     "sources": [
      "https://docs.python.org/3/library/asyncio-task.html"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "pablo-gonzalez",
     "name": "Pablo González",
     "sources": [
      "https://pablogonzalez.me/posts/python-code-review-guide/"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/pack-python#py-task-reference",
   "markdown_url": "https://goodcodereview.com/pack-python.md"
  },
  {
   "id": "py-test-hygiene",
   "level": "module",
   "status": "stable",
   "severity": "discuss",
   "concerns": [
    "testing",
    "reliability"
   ],
   "topics": [
    "python"
   ],
   "lang": "python",
   "applies_when": "A Python unit test touches the network, the shared filesystem, the database or the real clock. A per-test temporary directory (pytest `tmp_path`) is not the shared filesystem.",
   "context": [],
   "tool_gate": "pytest-socket (`--disable-socket`), plus a unit-test CI job with no database service and no network credentials",
   "note": "Google (small tests cannot reach the network or disk), Microsoft (a unit test has no file system or database and controls DateTime.Now), Spring (stub repositories, no runtime infrastructure), GitLab (mock outgoing requests, fake timers), Django (SimpleTestCase blocks database queries), Rails (travel_to, system tests in a separate CI step), Laravel (Unit tests cannot reach the database; most tests should be feature tests)",
   "lacks": null,
   "rule": "Keep unit tests off external I/O (the network, the shared filesystem and the database) and off the real clock, and run tests that need them in a separate, slower tier.",
   "why": "A test that reaches the network, the disk or the database is slow and fails for reasons outside the code: an offline CI runner, a schema change, a clock at midnight. Those dependencies belong in an integration tier, where the failure is about the integration. Google's small tests cannot reach the network or disk; Microsoft's unit tests have no file system or database and control the clock through a seam; Django's `SimpleTestCase` blocks database queries by default.",
   "counterpoint": "\"Most tests should hit the real stack, and mocking every collaborator hides real failures.\" Many teams keep most tests at the feature level and prefer real dependencies where they can keep them. Both hold: the rule is about the network, disk, database and clock boundary, not about doubling every collaborator. A feature or system test that boots the app belongs in the slower tier, and a team can run that tier on every merge. The unit tier stays fast and deterministic.",
   "fail": {
    "language": "python",
    "code": "def test_invoice_total(db):\n    db.execute(\"insert into line ...\")\n    assert invoice_total(1) == 12.0\n\ndef test_token_expires():\n    assert issue_token(ttl=60).expires_at > datetime.now()"
   },
   "pass": {
    "language": "python",
    "code": "def test_invoice_total(fake_repo, frozen_clock):\n    assert invoice_total(fake_repo) == 12.0\n\n# pytest --disable-socket; the integration tests run in their own CI job"
   },
   "sources": [
    "https://pablogonzalez.me/posts/python-code-review-guide/",
    "https://abseil.io/resources/swe-book/html/ch11.html",
    "https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices",
    "https://docs.spring.io/spring-framework/reference/testing/unit.html",
    "https://docs.gitlab.com/development/testing_guide/testing_levels/",
    "https://docs.djangoproject.com/en/5.1/topics/testing/tools/",
    "https://guides.rubyonrails.org/testing.html",
    "https://laravel.com/docs/12.x/testing"
   ],
   "orgs": [
    {
     "id": "google",
     "name": "Google",
     "sources": [
      "https://abseil.io/resources/swe-book/html/ch11.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "microsoft",
     "name": "Microsoft",
     "sources": [
      "https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices"
     ],
     "strength": "unjudged"
    },
    {
     "id": "spring",
     "name": "Spring",
     "sources": [
      "https://docs.spring.io/spring-framework/reference/testing/unit.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "gitlab",
     "name": "GitLab",
     "sources": [
      "https://docs.gitlab.com/development/testing_guide/testing_levels/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "django",
     "name": "Django Software Foundation",
     "sources": [
      "https://docs.djangoproject.com/en/5.1/topics/testing/tools/"
     ],
     "strength": "unjudged"
    },
    {
     "id": "rails",
     "name": "Ruby on Rails",
     "sources": [
      "https://guides.rubyonrails.org/testing.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "laravel",
     "name": "Laravel",
     "sources": [
      "https://laravel.com/docs/12.x/testing"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [
    {
     "id": "pablo-gonzalez",
     "name": "Pablo González",
     "sources": [
      "https://pablogonzalez.me/posts/python-code-review-guide/"
     ],
     "strength": "unjudged"
    }
   ],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/pack-python#py-test-hygiene",
   "markdown_url": "https://goodcodereview.com/pack-python.md"
  },
  {
   "id": "py-xdist-worker-isolated-state",
   "level": "module",
   "status": "stable",
   "severity": "should-fix",
   "concerns": [
    "testing",
    "reliability"
   ],
   "topics": [
    "python",
    "parallelism"
   ],
   "lang": "python",
   "applies_when": "A pytest suite runs in parallel with pytest-xdist (`-n`) and its tests use a database or another shared resource.",
   "context": [],
   "tool_gate": null,
   "note": "session fixtures run once per worker - pytest-xdist; each xdist process gets its own database by default - pytest-django; PyPI's suite hit cross-test contamination after -n auto and fixed it with a database per worker (191 s to 63 s) - Trail of Bits",
   "lacks": null,
   "rule": "Under pytest-xdist, give each worker its own database or other mutable resource, keyed by `worker_id`, and build an expensive shared one once with a lock file.",
   "why": "Each xdist worker collects its own tests, so a `session` fixture runs once per worker and not once per run. Workers that share one mutable database corrupt each other's tests, and the failures look random. PyPI's suite found this the moment it moved to `-n auto`; per-worker databases fixed it, and execution time fell from 191 s to 63 s.",
   "counterpoint": "\"Building a database per worker multiplies the setup cost.\" It does, by the number of workers, which is still far less than the number of tests. When one build is too slow even per worker, build it once under a file lock, as the xdist documentation shows, and copy it for each worker with a database template.",
   "fail": {
    "language": "python",
    "code": "@pytest.fixture(scope=\"session\")\ndef pg_db():\n    create_database(\"tests\")      # all workers create and use the same \"tests\" database\n    yield \"tests\""
   },
   "pass": {
    "language": "python",
    "code": "@pytest.fixture(scope=\"session\")\ndef pg_db(worker_id):             # \"gw0\", \"gw1\", ... or \"master\" without xdist\n    name = f\"tests-{worker_id}\"\n    create_database(name)\n    yield name\n    drop_database(name)"
   },
   "sources": [
    "https://pytest-xdist.readthedocs.io/en/stable/how-to.html",
    "https://pytest-django.readthedocs.io/en/latest/database.html",
    "https://blog.trailofbits.com/2025/05/01/making-pypis-test-suite-81-faster/"
   ],
   "orgs": [
    {
     "id": "pytest-xdist",
     "name": "pytest-xdist",
     "sources": [
      "https://pytest-xdist.readthedocs.io/en/stable/how-to.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "pytest-django",
     "name": "pytest-django",
     "sources": [
      "https://pytest-django.readthedocs.io/en/latest/database.html"
     ],
     "strength": "unjudged"
    },
    {
     "id": "trailofbits",
     "name": "Trail of Bits",
     "sources": [
      "https://blog.trailofbits.com/2025/05/01/making-pypis-test-suite-81-faster/"
     ],
     "strength": "unjudged"
    }
   ],
   "people": [],
   "backing": "unjudged",
   "url": "https://goodcodereview.com/pack-python#py-xdist-worker-isolated-state",
   "markdown_url": "https://goodcodereview.com/pack-python.md"
  }
 ]
}
