id: scale-review
name: Scale Review
tags: [data, safety, code-review, pr-time]
audience: dev
summary: Fires when a migration, schema, or aggregation pipeline changes. Forces a scalability audit — production-volume estimate, bounded queries, batched backfills — the "does it scale" check schema-migration leaves open.
description: |
  Fires when a migration, a Mongoose/SQL schema, or an aggregation pipeline is
  added or modified. These are the surfaces where a change that works on a
  developer's hundred rows silently breaks on production's millions: a backfill
  that loads a whole collection into memory and iterates it one document at a
  time, a schema with an unbounded array, an aggregation that scans a collection
  without an index-backed match.

  The sibling schema-migration policy gates reversibility, data loss, and index
  size — but it never asks "does this hold at our data volume?". This policy
  fills that hole. It loads the scalability-reviewer (the cross-cutting scale
  audit owner) alongside the database agent (the data-layer owner), and gates on
  a production-volume estimate, a bounded-query check, and a batched-backfill
  check.

  Gateway list endpoints (*.controller.ts) are deliberately NOT globbed here:
  pagination is already mandatory for them by convention (the pagination rule —
  skip/take pushed to the data layer, getPagination, get*AndCount), so a
  correctly-built list endpoint is bounded by construction and needs no gate. A
  glob over the ~328 controllers in backend-services would also fire on nearly
  every backend PR and dilute the gate into noise. The convention is backed
  deterministically by @bewith-dev/eslint-plugin (no-unbounded-find,
  no-collection-load-in-memory, require-batched-backfill) in the pre-commit lint
  hook and CI, and any residual unbounded list is caught by the always-on
  code-reviewer. This policy targets the surfaces the pagination convention does
  not cover: migrations, schema growth, and aggregation pipelines.

triggered_by:
  - file_change: "**/migrations/**"
  - file_change: "**/*.migration.ts"
  - file_change: "**/*.migration.sql"
  - file_change: "**/*.schema.ts"
  - file_change: "**/aggregations/**"
  - file_change: "**/aggregates/**"
  - file_change: "**/*.aggregate.ts"

risk:
  score: 8
  blast_radius: high
  reason: "A query or migration that is unbounded at scale degrades or exhausts memory across every consumer of the affected collection, and the failure surfaces only at production volume — long after the change passed local tests on a small dataset."

required_agents:
  - scalability-reviewer
  - database

required_gates:
  - id: data_volume_estimate
    description: "Estimate the production row/document count for every collection the change reads or writes, and state the volume the change must hold against"
  - id: bounded_query_check
    description: "Every query is bounded by a limit, pagination, or an index-backed filter — no full-collection load into application memory"
  - id: backfill_batched
    description: "Any backfill or migration over a large collection is cursor-based or batched, idempotent, and resumable — never a single in-memory load-and-iterate"

auto_workflows:
  - on: "unbounded_query_detected == true"
    action: "require-explicit-scale-justification-in-pr-body"

human_approval_required:
  - condition: "unbounded_query_detected"
