Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
270 changes: 180 additions & 90 deletions .dev-loop/INGEST_REPORT.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ follow the cross-pointers in their index or take the next matching seeded domain
| Domain | Status | Route here when |
|--------|--------|-----------------|
| [databases](wiki/databases/index.md) | **seeded** | Designing schemas/tables/keys, choosing or evaluating indexes, writing or optimizing queries, choosing transaction/isolation behavior |
| [backend](wiki/backend/index.md) | **seeded** | Server-side application code — language-agnostic (`common/`: API contracts, idempotency, JWT, timeouts/retries, caching, jobs, transactions in app code, shared state/pools, errors, LLM completion validation & context budgeting, consuming external-API responses, externally-owned defaults, object-storage references) plus stack subtrees: `java/` (JPA, Spring proxies, JVM threads/memory), `node/` (event loop, promises, runtime validation, shutdown), `python/` (GIL/asyncio, pydantic, WSGI/ASGI workers, language traps) |
| [backend](wiki/backend/index.md) | **seeded** | Server-side application code — language-agnostic (`common/`: API contracts, call-site enumeration before a contract change, idempotency, JWT, timeouts/retries, caching, jobs, transactions in app code, shared state/pools, errors, LLM completion validation & context budgeting, consuming external-API responses, externally-owned defaults, object-storage references) plus stack subtrees: `java/` (JPA, Spring proxies, JVM threads/memory), `node/` (event loop, promises, runtime validation, shutdown), `python/` (GIL/asyncio, pydantic, WSGI/ASGI workers, language traps) |
| [frontend](wiki/frontend/index.md) | **seeded** | Web UI code: state placement, rendering performance, in-UI data fetching (races, infinite scroll), auth token handling, forms, XSS-safe output, accessibility |
| [infrastructure](wiki/infrastructure/index.md) | **seeded** | CI/CD pipelines, secrets in build/deploy, container image builds, rollout/rollback strategy, observability (logs/metrics/alerting) |
| [testing](wiki/testing/index.md) | **seeded** | Writing or structuring automated tests: level choice, cases/assertions, test data, mock decisions, flaky tests (release-process quality → qa) |
Expand Down
1 change: 1 addition & 0 deletions log.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ Append-only. Format: `## [YYYY-MM-DD] <ingest|revise|lint|gap|contradiction|drif
## [2026-08-03] ingest | Consolidated review of knowledge PRs #6–#13 (8 fork PRs) into 12 pages. New: backend/common/llm (completion-response-validation, context-window-budget), backend/common/integrations (externally-owned-defaults), backend/common/storage (object-key-persistence), infrastructure/containers/host-cgroup-visibility, infrastructure/observability/missing-container-metrics, platforms/environment/unicode-text-matching, platforms/shells/command-text-inspected-before-execution, platforms/processes/non-interactive-cli-invocation, qa/document-verification (spec-document-gates, editing-a-gated-document), testing/quality (checks-that-cannot-pass, spec-artifact-checks, harness-reverse-controls). All cited URLs are per-PR live-verified; three pages were reconciled from two overlapping PR versions each (see revise/dedup entries below).
## [2026-08-03] revise | Reconciled 3 pages from overlapping PR pairs, taking the more complete/better-sourced body and folding in the other's unique cases: backend/common/llm/completion-response-validation (#12 body — tool_calls/function_call carve-out, streaming, Responses API status==incomplete, "reasoning is scratch, not deliverable" — kept in llm/ per #6/#13 category, folded in #6's DeepSeek-first-party edge + the 8,173-char reasoning_content field incident); backend/common/integrations/externally-owned-defaults (#12 generalized body — any repo-external resource — folded in #6's LiteLLM-alias-removed field incident + gateway-config-vs-live-upstream nuance); platforms/processes/non-interactive-cli-invocation (#12 body — GNU-nohup extension precision, ssh -n stdin-detach vs BatchMode, pre-log DNS/TLS/proxy + curl -v — folded in #11's DEBIAN_FRONTEND, pager/color TTY case, wrapper-CLI case, and the no-request-in-gateway-log field incident).
## [2026-08-03] dedup | Dropped 3 candidate pages as duplicates/superseded during the #6–#13 consolidation: testing/docs-as-spec/document-conformance-checks (#9 — same case as testing/quality/spec-artifact-checks from #8: coverage-vs-validity split, per-check negative controls, GFM pipe parsing; #8 kept as canonical, docs-as-spec category not created); backend/common/llm/gateway-model-alias-defaults (#6 — subsumed by the generalized integrations/externally-owned-defaults; the model-alias case is one instance); backend/common/integrations/llm-response-completeness (#12 — folded into llm/completion-response-validation, kept in llm/ for category coherence with context-window-budget).
## [2026-08-04] ingest | knowledge-flush of 3 queued insights. New: testing/quality/schema-additions-under-a-golden-gate (a new node kind absent from the golden example is untouched by every mutant derived from it — commit a minimal conforming fixture, one negative per keyword the new branch adds, restore each and require red; judge the suite by whether anything loads the schema at all), backend/common/change-impact/call-site-enumeration (NEW category change-impact: enumerate by callee name, treat a parameter-name search as a partial index — positional-or-keyword is Python's default parameter kind and ast.Call keeps args/keywords in separate fields; re-run the enumeration after the edit). Merged: testing/data/test-data-and-isolation +1 Do row / +1 edge case / +1 Instead-of (a factory whose shape depends on a value the test also passes to the code under test takes it as a parameter, never as a module-level default). All cited URLs live-verified this session; one local reproduction (Python 3.14.6: keyword search 1 hit vs AST callee enumeration 4).
## [2026-08-04] ingest | knowledge-flush: 3 queued session insights merged into existing pages (no new pages — merge-before-create). platforms/environment/path-resolution +keg-only case: a package manager reports a tool installed but `command -v`/`which` find nothing because keg-only/unlinked formulae (llvm, openssl, curl) are deliberately off PATH — run from `$(brew --prefix)/opt/<f>/bin` (verified: `which mlir-opt` not found vs `/opt/homebrew/opt/llvm/bin/mlir-opt` → LLVM 22.1.8; source docs.brew.sh/FAQ). platforms/processes/non-interactive-cli-invocation +bracketed-paste edge: injecting a long/multiline prompt into a REPL (tmux `send-keys -l`) stalls at `❯ [Pasted text #1]` because the input is one bracketed-paste block (ESC[200~…201~) whose embedded newline is not submit — send Enter as a separate keystroke a beat later (source en.wikipedia.org/wiki/Bracketed-paste + claude-code#43169). testing/strategy/test-level-choice +import-side-effect edge/instead-of: a "pure" function's test is not dependency-free if its module runs I/O at import; `@pytest.mark.skipif` evaluates after the module import so it can't gate it — use `importorskip`/`skip(allow_module_level=True)` or move the function to a side-effect-free module (source docs.pytest.org skipping). Harvested "infrastructure" hint for the keg-only insight re-routed to platforms/environment (dedicated PATH page). Confidence: keg-only & pytest verified vs official docs; bracketed-paste mechanism doc-verified, the claude-CLI submit specifics field-tested.
83 changes: 83 additions & 0 deletions wiki/backend/common/change-impact/call-site-enumeration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
id: backend-common-change-impact-call-site-enumeration
domain: backend
category: change-impact
applies_to: [general]
confidence: verified
sources:
- https://docs.python.org/3/glossary.html
- https://docs.python.org/3/library/ast.html
- https://peps.python.org/pep-0570/
last_verified: 2026-08-04
related: [qa-process-regression-scope, backend-python-language-mutable-state-traps]
---

# Enumerating Call Sites Before Changing a Callee's Contract

## When this applies

You are about to change the contract of a function, method, or constructor that
other code calls — adding, removing, reordering, or redefining a parameter — and
the plan depends on having the complete list of call sites. Also when a
migration you scoped from a search came back green and then failed on call sites
the search never listed.

## Do this

1. **Enumerate by the callee's name (`verify(`, `Interpreter(`) and read every
hit.** Treat a search for a parameter name (`repo_rows=`) as a partial index:
it lists only the sites that happen to pass that argument by keyword.

2. **Read the partiality as a language property, not a search-quality problem.**
Python's default parameter kind is positional-or-keyword — it "specifies an
argument that can be passed either positionally or as a keyword argument.
This is the default kind of parameter". The parse tree keeps the two forms in
separate fields: in `ast.Call`, "`args` holds a list of the arguments passed
by position" while "`keywords` holds a list of `keyword` objects representing
arguments passed by keyword". A keyword-name search reads `keywords` only.

3. **Pick the enumeration handle from the callee's shape:**

| Callee | Enumerate by |
|--------|--------------|
| A distinctively named function | The name plus `(`, across the whole repo including tests, fixtures, and scripts |
| A name common enough to collide (`run`, `get`, `verify`) | The language server's find-references, or an AST pass collecting `Call` nodes whose `func` resolves to it — text search cannot separate the homonyms |
| A constructor | The class name plus `(`, plus each import form that renames it (`from m import C as D` → `D(`) |
| Something passed as a value (callback, decorator, registry entry, `functools.partial`) | The bare name without `(` as well — the argument list at those sites lives where the value is invoked, not where it is referenced |
| Dispatched dynamically (`getattr`, a name in config/YAML) | The string form too, and record in the plan that this class of site is not statically enumerable |

4. **State the method next to the count.** "13 call sites (grep `verify(`,
including tests)" is checkable; "13 call sites" is not, and a plan built on
an unstated method cannot be reviewed for this gap.

5. **Re-run the same enumeration after the edit and require zero old-contract
sites**, then run the tests. The re-run is what converts the enumeration from
a plan input into a completion check.

## Edge cases

| Case | Then |
|------|------|
| The new parameter can be keyword-only | Declare it after a bare `*`; a stale positional call then fails at the call site instead of silently binding to the wrong parameter (PEP 570 defines the `/` and `*` markers that fix a parameter's passing form) |
| A parameter is inserted before existing ones rather than appended | Every positional site rebinds silently and none of them changes text — a keyword search cannot bound the risk, so a full callee-name enumeration is the only scope; prefer appending |
| The callee is re-exported through a package `__init__` or a facade | Enumerate the re-exported name as well; sites importing through the facade never mention the defining module |
| Call sites live in another repository or a published package | The change is a versioned deprecation, not an in-place edit: keep the old contract accepting its old shape for a release, and enumerate what you own now |
| The language has no keyword arguments at all (JavaScript, Go) | Every site is positional, so a parameter-name search returns nothing at all — enumerate by callee name from the start |
| The repo has no working language server for the language | Enumerate by callee name and say so; an AST pass over `Call` nodes is the fallback that survives aliasing |

## Instead of

| If you are about to | Do this instead | Why |
|---------------------|-----------------|-----|
| Report "N call sites, M need editing" from a search for the parameter name | Search the callee name, read each hit, and report the method with the count | Positional-or-keyword is the default parameter kind, so a keyword search is blind to every site that passes the argument by position |
| Treat a green suite after a partial migration as proof the migration is complete | Re-run the callee-name enumeration and require zero old-contract sites | The suite exercises the sites it reaches; the ones the recon missed are the ones that break later |
| Scope a contract change from the plan's original recon | Re-enumerate at edit time | Call sites are added between planning and editing, and the plan's count is what makes the omission invisible |
| Add the parameter in the middle of the signature and update the keyword sites | Append it, or make it keyword-only, then migrate every enumerated site | A middle insertion rebinds existing positional arguments without changing a character at those sites |

## Sources

- https://docs.python.org/3/glossary.html — *positional-or-keyword*: "specifies an argument that can be passed either positionally or as a keyword argument. This is the default kind of parameter"; *keyword-only* requires a bare `*`, *positional-only* a `/`
- https://docs.python.org/3/library/ast.html — `ast.Call`: "`args` holds a list of the arguments passed by position", "`keywords` holds a list of `keyword` objects representing arguments passed by keyword" — the two forms are distinct fields, so a keyword-text search cannot reach positional arguments
- https://peps.python.org/pep-0570/ — the `/` marker for positional-only parameters, alongside the existing `*` marker for keyword-only, as the way a signature fixes how an argument may be passed
- Local reproduction 2026-08-04 (Python 3.14.6, macOS): over four call sites of `verify(...)` where one passes `repo_rows=` by keyword, a regex search for `repo_rows\s*=` matches 1 while an AST pass over `Call` nodes named `verify` finds 4 — 3 sites invisible to the keyword search
- Field incident 2026-08-04 (`linkly-t1-repo-policy`, Python): recon by keyword search reported "13 call sites, 7 need editing"; 8 further seeds passed the same value as `verify()`'s fourth positional argument, and the suite the session had reported green then ran `472 tests / FAILED (failures=11)`
8 changes: 7 additions & 1 deletion wiki/backend/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ three stack subtrees — route by concern first, stack second:

| Subtree | Route there when |
|---------|------------------|
| [common](#common-language-agnostic) (below) | The concern is language-agnostic: API contracts, idempotency, JWT issuance, outbound calls, caching, jobs, transactions in app code, shared state/pools, exception structure, LLM completion validation & context budgeting, consuming external-API responses, externally-owned defaults, object-storage references |
| [common](#common-language-agnostic) (below) | The concern is language-agnostic: API contracts, enumerating call sites before a contract change, idempotency, JWT issuance, outbound calls, caching, jobs, transactions in app code, shared state/pools, exception structure, LLM completion validation & context budgeting, consuming external-API responses, externally-owned defaults, object-storage references |
| [java](java/index.md) | You are writing/reviewing JVM backend code (Java/Kotlin, Spring, JPA/Hibernate) and the concern is stack-specific: entity mapping, persistence context, proxy pitfalls, JVM threads/memory |
| [node](node/index.md) | You are writing/reviewing Node.js/TypeScript backend code: event-loop blocking, promise error handling, runtime validation at boundaries, graceful shutdown |
| [python](python/index.md) | You are writing/reviewing Python backend code: GIL/concurrency model, pydantic validation, WSGI/ASGI workers, language traps |
Expand All @@ -26,6 +26,12 @@ Match your situation to a "load when" line; load only matching pages.
| [idempotency](common/api-design/idempotency.md) | An endpoint with side effects (create, charge, send) can receive the same request twice — client retry after timeout, user double-submit, gateway retry; designing idempotency-key storage; deciding which operations are safe to retry |
| [pagination-contract](common/api-design/pagination-contract.md) | Designing a list endpoint's request/response contract — cursor vs page-number, limit caps, total counts, expired-cursor behavior (the backing SQL/index → databases/query-optimization/keyset-pagination) |

### change-impact

| Page | Load when |
|------|-----------|
| [call-site-enumeration](common/change-impact/call-site-enumeration.md) | Changing the contract of a function/method/constructor other code calls — adding, removing, reordering or redefining a parameter — and you need the complete call-site list; scoping such a migration from a search; a migration scoped from recon came back green and then failed on call sites the search never listed; deciding whether to append a parameter or make it keyword-only (release-level re-test scope → qa/process/regression-scope) |

### reliability

| Page | Load when |
Expand Down
4 changes: 2 additions & 2 deletions wiki/qa/process/regression-scope.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ confidence: field-tested
sources:
- https://martinfowler.com/articles/rise-test-impact-analysis.html
last_verified: 2026-07-10
related: [qa-process-release-gates, qa-bug-reports-reproducible-reports]
related: [qa-process-release-gates, qa-bug-reports-reproducible-reports, backend-common-change-impact-call-site-enumeration]
---

# Choosing What to Re-Test for a Change
Expand Down Expand Up @@ -44,7 +44,7 @@ Two standing practices that make the rings work:

| Case | Then |
|------|------|
| The change is in code with no test coverage and unclear callers | Trace callers before scoping (text pointer wiki/testing/ for coverage tooling); an adjacency you cannot enumerate defaults into scope |
| The change is in code with no test coverage and unclear callers | Trace callers before scoping ([backend-common-change-impact-call-site-enumeration] for enumerating them; text pointer wiki/testing/ for coverage tooling); an adjacency you cannot enumerate defaults into scope |
| Data migration or backfill ships with the release | Add the migrated data's read paths to the direct ring — the "change" is the data, and its consumers are the changed feature |
| Two changes in one release touch the same adjacency | Test that adjacency once, after both changes are in the release build — testing between them validates a build that will never ship |
| Config-only or copy-only change | Direct ring only: verify the changed value/text where it surfaces, plus the critical-flows floor if the config gates a critical flow |
Expand Down
Loading
Loading