@nklisch/pi-agile-workflow 0.15.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/.claude-plugin/plugin.json +10 -0
  2. package/.codex-plugin/plugin.json +28 -0
  3. package/CHANGELOG.md +591 -0
  4. package/README.md +107 -0
  5. package/docs/ARCHITECTURE.md +716 -0
  6. package/docs/MIGRATION.md +419 -0
  7. package/docs/PRINCIPLES.md +464 -0
  8. package/docs/ROADMAP.md +95 -0
  9. package/docs/SPEC.md +855 -0
  10. package/docs/VISION.md +106 -0
  11. package/extensions/agile-workflow.test.ts +419 -0
  12. package/extensions/agile-workflow.ts +467 -0
  13. package/hooks/hooks.json +52 -0
  14. package/hooks/scripts/prompt-context.py +628 -0
  15. package/hooks/scripts/substrate-maintainer.py +359 -0
  16. package/hooks/scripts/test_prompt_context.py +757 -0
  17. package/package.json +30 -0
  18. package/scripts/install-work-view.sh +165 -0
  19. package/scripts/tests/agent-metadata.test.sh +75 -0
  20. package/scripts/tests/bump-version.test.sh +552 -0
  21. package/scripts/tests/channel-parity.test.sh +117 -0
  22. package/scripts/tests/convert-content-integrity.test.sh +246 -0
  23. package/scripts/tests/convert-install-routing.test.sh +159 -0
  24. package/scripts/tests/install-work-view.test.sh +411 -0
  25. package/scripts/tests/pi-package-metadata.test.sh +119 -0
  26. package/scripts/tests/work-board-shim.test.sh +239 -0
  27. package/scripts/tests/work-view-dist-version.test.sh +105 -0
  28. package/scripts/work-board.sh +64 -0
  29. package/scripts/work-view.sh +423 -0
  30. package/skills/autopilot/SKILL.md +330 -0
  31. package/skills/autopilot/agents/openai.yaml +6 -0
  32. package/skills/board/SKILL.md +62 -0
  33. package/skills/board/agents/openai.yaml +6 -0
  34. package/skills/bold-refactor/SKILL.md +244 -0
  35. package/skills/bold-refactor/agents/openai.yaml +6 -0
  36. package/skills/bug-scan/SKILL.md +378 -0
  37. package/skills/bug-scan/agents/openai.yaml +6 -0
  38. package/skills/bug-scan/references/async-promises.md +119 -0
  39. package/skills/bug-scan/references/concurrency-races.md +148 -0
  40. package/skills/bug-scan/references/data-layer.md +167 -0
  41. package/skills/bug-scan/references/error-handling.md +197 -0
  42. package/skills/bug-scan/references/gate-item-template.md +94 -0
  43. package/skills/bug-scan/references/language-footguns.md +219 -0
  44. package/skills/bug-scan/references/parked-item-template.md +103 -0
  45. package/skills/bug-scan/references/report-template.md +115 -0
  46. package/skills/bug-scan/references/resource-leaks.md +199 -0
  47. package/skills/bug-scan/references/state-closures.md +148 -0
  48. package/skills/bug-scan/references/time-numbers.md +158 -0
  49. package/skills/convert/SKILL.md +1463 -0
  50. package/skills/convert/agents/openai.yaml +6 -0
  51. package/skills/convert/references/legacy-overlap-migration.md +179 -0
  52. package/skills/deep-code-scan/SKILL.md +378 -0
  53. package/skills/deep-code-scan/agents/openai.yaml +6 -0
  54. package/skills/deep-code-scan/references/artifact-ledger.md +148 -0
  55. package/skills/deep-code-scan/references/consolidation.md +91 -0
  56. package/skills/deep-code-scan/references/decomposition.md +127 -0
  57. package/skills/deep-code-scan/references/item-templates.md +263 -0
  58. package/skills/deep-code-scan/references/lane-catalog.md +129 -0
  59. package/skills/deep-code-scan/references/review-gauntlet.md +108 -0
  60. package/skills/deep-code-scan/references/scanner-brief.md +152 -0
  61. package/skills/e2e-test-design/SKILL.md +517 -0
  62. package/skills/e2e-test-design/references/anti-tautology.md +261 -0
  63. package/skills/e2e-test-design/references/service-mocks.md +234 -0
  64. package/skills/e2e-test-design/references/test-taxonomy.md +211 -0
  65. package/skills/epic-design/SKILL.md +476 -0
  66. package/skills/epicize/SKILL.md +210 -0
  67. package/skills/epicize/agents/openai.yaml +6 -0
  68. package/skills/feature-design/SKILL.md +472 -0
  69. package/skills/fix/SKILL.md +173 -0
  70. package/skills/gate-cruft/SKILL.md +258 -0
  71. package/skills/gate-docs/SKILL.md +278 -0
  72. package/skills/gate-patterns/SKILL.md +389 -0
  73. package/skills/gate-refactor/SKILL.md +408 -0
  74. package/skills/gate-security/SKILL.md +275 -0
  75. package/skills/gate-tests/SKILL.md +367 -0
  76. package/skills/groom/SKILL.md +162 -0
  77. package/skills/groom/agents/openai.yaml +6 -0
  78. package/skills/ideate/SKILL.md +185 -0
  79. package/skills/ideate/agents/openai.yaml +6 -0
  80. package/skills/implement/SKILL.md +288 -0
  81. package/skills/implement-orchestrator/SKILL.md +604 -0
  82. package/skills/park/SKILL.md +111 -0
  83. package/skills/perf-design/SKILL.md +427 -0
  84. package/skills/perf-scout/SKILL.md +404 -0
  85. package/skills/perf-scout/agents/openai.yaml +6 -0
  86. package/skills/perf-scout/references/algorithmic-and-data-structures.md +126 -0
  87. package/skills/perf-scout/references/approximation-and-precomputation.md +136 -0
  88. package/skills/perf-scout/references/caching-and-memoization.md +123 -0
  89. package/skills/perf-scout/references/compiler-runtime-and-language.md +136 -0
  90. package/skills/perf-scout/references/database-and-storage-internals.md +144 -0
  91. package/skills/perf-scout/references/distributed-systems.md +150 -0
  92. package/skills/perf-scout/references/game-engine-and-realtime.md +136 -0
  93. package/skills/perf-scout/references/gpu-and-accelerators.md +130 -0
  94. package/skills/perf-scout/references/idea-ranking.md +75 -0
  95. package/skills/perf-scout/references/io-and-batching.md +135 -0
  96. package/skills/perf-scout/references/memory-and-data-locality.md +137 -0
  97. package/skills/perf-scout/references/parallelism-and-vectorization.md +126 -0
  98. package/skills/perf-scout/references/parked-item-template.md +117 -0
  99. package/skills/perf-scout/references/peer-review-pass.md +150 -0
  100. package/skills/perf-scout/references/report-template.md +148 -0
  101. package/skills/principles/SKILL.md +678 -0
  102. package/skills/principles/references/models.md +191 -0
  103. package/skills/principles/references/subagents.md +158 -0
  104. package/skills/prose-author/SKILL.md +185 -0
  105. package/skills/refactor-conventions-creator/SKILL.md +359 -0
  106. package/skills/refactor-conventions-creator/agents/openai.yaml +6 -0
  107. package/skills/refactor-conventions-creator/references/common-structures.md +91 -0
  108. package/skills/refactor-conventions-creator/references/common-styles.md +90 -0
  109. package/skills/refactor-design/SKILL.md +384 -0
  110. package/skills/release-deploy/SKILL.md +667 -0
  111. package/skills/release-deploy/agents/openai.yaml +6 -0
  112. package/skills/research/SKILL.md +166 -0
  113. package/skills/research/agents/openai.yaml +6 -0
  114. package/skills/review/SKILL.md +267 -0
  115. package/skills/review/references/deep-review.md +98 -0
  116. package/skills/review/references/review-lenses.md +68 -0
  117. package/skills/review/references/substrate-side-effects.md +161 -0
  118. package/skills/review/references/target-resolution.md +52 -0
  119. package/skills/scope/SKILL.md +486 -0
  120. package/work-view/Cargo.lock +486 -0
  121. package/work-view/Cargo.toml +9 -0
  122. package/work-view/crates/cli/.work-view-version +1 -0
  123. package/work-view/crates/cli/Cargo.toml +18 -0
  124. package/work-view/crates/cli/src/actionable.rs +500 -0
  125. package/work-view/crates/cli/src/args.rs +792 -0
  126. package/work-view/crates/cli/src/board/assets/board.css +855 -0
  127. package/work-view/crates/cli/src/board/assets/board.js +259 -0
  128. package/work-view/crates/cli/src/board/assets/card.js +139 -0
  129. package/work-view/crates/cli/src/board/assets/components.css +290 -0
  130. package/work-view/crates/cli/src/board/assets/dependency.js +1193 -0
  131. package/work-view/crates/cli/src/board/assets/detail.js +256 -0
  132. package/work-view/crates/cli/src/board/assets/filters.js +389 -0
  133. package/work-view/crates/cli/src/board/assets/index.html +77 -0
  134. package/work-view/crates/cli/src/board/assets/kanban.js +231 -0
  135. package/work-view/crates/cli/src/board/assets/markdown.js +260 -0
  136. package/work-view/crates/cli/src/board/assets/motion.css +131 -0
  137. package/work-view/crates/cli/src/board/assets/state.js +245 -0
  138. package/work-view/crates/cli/src/board/assets/table.js +304 -0
  139. package/work-view/crates/cli/src/board/assets/tokens.css +602 -0
  140. package/work-view/crates/cli/src/board/assets/views.js +41 -0
  141. package/work-view/crates/cli/src/board/assets.rs +162 -0
  142. package/work-view/crates/cli/src/board/feed.rs +204 -0
  143. package/work-view/crates/cli/src/board/mod.rs +246 -0
  144. package/work-view/crates/cli/src/board/open.rs +144 -0
  145. package/work-view/crates/cli/src/board/server.rs +407 -0
  146. package/work-view/crates/cli/src/main.rs +195 -0
  147. package/work-view/crates/cli/src/render.rs +354 -0
  148. package/work-view/crates/cli/src/scope.rs +157 -0
  149. package/work-view/crates/cli/src/stale.rs +581 -0
  150. package/work-view/crates/cli/tests/board-js/dependency-table.test.mjs +64 -0
  151. package/work-view/crates/cli/tests/board-js/expanded-browsing.test.mjs +79 -0
  152. package/work-view/crates/cli/tests/board-js/harness-smoke.test.mjs +11 -0
  153. package/work-view/crates/cli/tests/board-js/harness.mjs +437 -0
  154. package/work-view/crates/cli/tests/board-js/kanban-detail.test.mjs +75 -0
  155. package/work-view/crates/cli/tests/board-js/markdown-filter.test.mjs +60 -0
  156. package/work-view/crates/cli/tests/fixtures/golden/.work/CONVENTIONS.md +3 -0
  157. package/work-view/crates/cli/tests/fixtures/golden/.work/active/epics/epic-alpha.md +16 -0
  158. package/work-view/crates/cli/tests/fixtures/golden/.work/active/features/feat-a.md +16 -0
  159. package/work-view/crates/cli/tests/fixtures/golden/.work/active/features/feat-b.md +16 -0
  160. package/work-view/crates/cli/tests/fixtures/golden/.work/active/stories/story-alpha-1.md +16 -0
  161. package/work-view/crates/cli/tests/fixtures/golden/.work/active/stories/story-research-1.md +19 -0
  162. package/work-view/crates/cli/tests/fixtures/golden/.work/archive/feat-done.md +16 -0
  163. package/work-view/crates/cli/tests/fixtures/golden/.work/archive/feat-shipped.md +18 -0
  164. package/work-view/crates/cli/tests/fixtures/golden/.work/backlog/idea-backlog.md +7 -0
  165. package/work-view/crates/cli/tests/fixtures/golden/.work/releases/v1.0/v1.0.md +16 -0
  166. package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/CONVENTIONS.md +6 -0
  167. package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/features/feat-dep-done.md +17 -0
  168. package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/features/feat-design-ready.md +17 -0
  169. package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/features/feat-impl-ready.md +17 -0
  170. package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/stories/story-review-ready.md +17 -0
  171. package/work-view/crates/cli/tests/fixtures/with-malformed/.work/CONVENTIONS.md +4 -0
  172. package/work-view/crates/cli/tests/fixtures/with-malformed/.work/active/epics/malformed-no-closing-fence.md +8 -0
  173. package/work-view/crates/cli/tests/fixtures/with-malformed/.work/active/features/good-item.md +16 -0
  174. package/work-view/crates/cli/tests/integration.rs +2833 -0
  175. package/work-view/crates/core/Cargo.toml +13 -0
  176. package/work-view/crates/core/src/error.rs +61 -0
  177. package/work-view/crates/core/src/filter.rs +804 -0
  178. package/work-view/crates/core/src/graph.rs +324 -0
  179. package/work-view/crates/core/src/index.rs +590 -0
  180. package/work-view/crates/core/src/lib.rs +54 -0
  181. package/work-view/crates/core/src/model.rs +202 -0
  182. package/work-view/crates/core/src/parse.rs +494 -0
  183. package/work-view/crates/core/tests/fixtures/golden/.work/CONVENTIONS.md +15 -0
  184. package/work-view/crates/core/tests/fixtures/golden/.work/active/epics/epic-core.md +16 -0
  185. package/work-view/crates/core/tests/fixtures/golden/.work/active/features/feat-parser.md +16 -0
  186. package/work-view/crates/core/tests/fixtures/golden/.work/active/features/feat-query.md +16 -0
  187. package/work-view/crates/core/tests/fixtures/golden/.work/active/stories/malformed-no-id.md +15 -0
  188. package/work-view/crates/core/tests/fixtures/golden/.work/active/stories/story-parse-unit.md +16 -0
  189. package/work-view/crates/core/tests/fixtures/golden/.work/active/stories/story-security-gate.md +16 -0
  190. package/work-view/crates/core/tests/fixtures/golden/.work/archive/feat-old.md +16 -0
  191. package/work-view/crates/core/tests/fixtures/golden/.work/archive/feat-parser.md +17 -0
  192. package/work-view/crates/core/tests/fixtures/golden/.work/backlog/idea-future-board.md +7 -0
  193. package/work-view/crates/core/tests/fixtures/golden/.work/releases/v1.0/v1.0.md +16 -0
  194. package/work-view/crates/core/tests/fixtures/precedence/.work/CONVENTIONS.md +10 -0
  195. package/work-view/crates/core/tests/fixtures/precedence/.work/archive/dup-item.md +18 -0
  196. package/work-view/crates/core/tests/fixtures/precedence/.work/releases/v2.0/dup-item.md +18 -0
  197. package/work-view/crates/core/tests/integration.rs +541 -0
  198. package/work-view/dist/.gitattributes +1 -0
  199. package/work-view/dist/README.md +42 -0
  200. package/work-view/dist/aarch64-apple-darwin/work-view +0 -0
  201. package/work-view/dist/aarch64-unknown-linux-musl/work-view +0 -0
  202. package/work-view/dist/x86_64-apple-darwin/work-view +0 -0
  203. package/work-view/dist/x86_64-unknown-linux-musl/work-view +0 -0
@@ -0,0 +1,167 @@
1
+ # Data Layer & Distributed Systems — Bug Reference
2
+
3
+ > **When to load this reference**: when scanning code that touches a database (SQL or NoSQL),
4
+ > an ORM, a message queue, an external service, or coordinates work across processes/services.
5
+
6
+ ## Detection signals
7
+ - SQL / query shapes: `SELECT .* FROM`, `UPDATE .* SET`, `DELETE FROM`, `INSERT INTO`, `BEGIN`, `COMMIT`, `ROLLBACK`, `FOR UPDATE`, `LOCK TABLE`, raw string concat into queries (`f"SELECT ... {x}"`, `"... " + var`).
8
+ - Transaction blocks: `.transaction(`, `@Transactional`, `with db.atomic`, `session.begin`, `BEGIN;`, `tx.Begin()`, `conn.beginTransaction`.
9
+ - ORM call sites: `.findAll(`, `.findMany(`, `.includes(`, `.preload(`, `.eager_load(`, `.joins(`, `.select_related(`, `.prefetch_related(`, `lazy=`, `relationship(`, `belongsTo(`, `hasMany(`, `@OneToMany`, `@ManyToOne`.
10
+ - ORM model files: `models.py`, `*.entity.ts`, `schema.prisma`, `*/models/*.rb`, `drizzle/schema.ts`.
11
+ - Iteration over query results: `for row in qs:` followed by `row.related.something`, `users.map(u => u.posts)`, template loops with `{{ obj.related.field }}`.
12
+ - Migrations: `migrations/`, `alembic/`, `prisma migrate`, `knex migrate`, `ALTER TABLE`, `DROP COLUMN`, `ADD COLUMN NOT NULL`.
13
+ - Distributed primitives: `retry`, `withRetry`, `backoff`, `CircuitBreaker`, `kafka`, `rabbitmq`, `sqs`, `pubsub`, `webhook`, `consumer.poll`, `producer.send`, `outbox`, `saga`.
14
+ - Time/clock: `now()`, `Date.now()`, `time.Now()`, `System.currentTimeMillis`, `Instant.now()` used for ordering or correlation across services.
15
+ - Cache layer: `Redis.`, `memcache`, `\.invalidate(`, `\.del(`, `cache.get`, `getOrSet`, `SETEX`, `EXPIRE`.
16
+ - Hot files: repositories, DAOs, query helpers, batch jobs, webhook receivers, queue consumers, retry helpers, cache wrappers.
17
+
18
+ ## Data Layer Patterns
19
+
20
+ ### 1. N+1 query
21
+ - **Signature**: a query producing a list, then a loop touching a related field — `for u in users: print(u.posts)`, `users.map(u => u.profile)`, template `{% for x in qs %}{{ x.author.name }}{% endfor %}`.
22
+ - **Why hard to find**: each individual query is cheap; total cost only visible under realistic data volume. ORMs lazy-load silently.
23
+ - **Where to look**: list endpoints, admin pages, serializers, GraphQL resolvers without DataLoader, template render paths, JSON serialization of nested objects.
24
+ - **Example**:
25
+ ```python
26
+ users = User.objects.all()
27
+ return [{ "name": u.name, "posts": [p.title for p in u.posts.all()] } for u in users]
28
+ ```
29
+ - **Fix direction**: eager-load relations (`select_related` / `prefetch_related`, Prisma `include`, Drizzle relational query, SQLAlchemy `joinedload` / `selectinload`, ActiveRecord `includes`); for GraphQL use DataLoader-style batching.
30
+ - **Variants**: Prisma — silent N+1 when iterating without `include`; Drizzle — no auto-eager-load, must use relational queries; SQLAlchemy — `lazy="select"` default; Django — `.only()` without `select_related` triggers per-row fetch; ActiveRecord — `each` + association = trap.
31
+
32
+ ### 2. Missing or wrong index
33
+ - **Signature**: `WHERE col = ?` on a non-indexed column; `LIKE '%foo%'` (unanchored); `ORDER BY` on non-indexed column; partial index whose predicate doesn't match the query.
34
+ - **Why hard to find**: works fine in dev with small tables; full-scan latency scales with row count and bites at production scale.
35
+ - **Where to look**: schema/migration files, query planner output, slow-query logs, columns used in `WHERE` / `JOIN` / `ORDER BY` not listed in `CREATE INDEX`, partial indexes (`WHERE deleted_at IS NULL`) used by queries that don't reuse the same predicate.
36
+ - **Fix direction**: add covering / composite index matching query shape; verify with `EXPLAIN` / `EXPLAIN ANALYZE`; for partial indexes, ensure callers include the predicate verbatim.
37
+ - **Variants**: Postgres partial index requires matching `WHERE` in query; MySQL leftmost-prefix rule for composite indexes; Mongo compound index order matters; case-insensitive search needs functional index or `citext`.
38
+
39
+ ### 3. Transaction held across external I/O
40
+ - **Signature**: `BEGIN` (or `@Transactional`) wrapping an HTTP call, S3 upload, email send, queue publish, or sleep.
41
+ - **Why hard to find**: works under low load; under load the held row/page locks cascade into pool exhaustion and deadlock spikes.
42
+ - **Where to look**: service methods that mix DB writes with `fetch(`, `requests.`, `httpClient.`, `SES.send`, `s3.putObject`, `kafka.send` inside one transactional boundary.
43
+ - **Example**: charging a payment gateway inside `@Transactional` — gateway succeeds, DB rollback leaves money taken without record.
44
+ - **Fix direction**: do external work outside the transaction; use the **transactional outbox** (write event row in the same tx, deliver async) for at-least-once; compensate on failure rather than relying on rollback.
45
+
46
+ ### 4. Wrong isolation level / missing row lock
47
+ - **Signature**: read-modify-write across two statements at `READ COMMITTED` without `SELECT ... FOR UPDATE`; check-then-insert without unique constraint; balance update via `UPDATE accounts SET balance = ? WHERE id = ?` after a non-locking `SELECT`.
48
+ - **Why hard to find**: phantom reads, lost updates, write skew — only show under concurrent traffic.
49
+ - **Where to look**: financial ledgers, inventory decrement, counter increments, "reserve a slot" code, get-or-create flows.
50
+ - **Fix direction**: prefer single-statement atomic updates (`UPDATE ... SET balance = balance - ?`); when not possible, use `SELECT ... FOR UPDATE` or bump isolation to `REPEATABLE READ` / `SERIALIZABLE`; add unique constraints for idempotency.
51
+ - **Variants**: MySQL default `REPEATABLE READ` allows write skew; Postgres `SERIALIZABLE` retries on serialization failure (handle `40001`); SQLite single-writer hides the bug locally.
52
+
53
+ ### 5. ORM lazy-load at render time
54
+ - **Signature**: serializer / template accesses `.related` outside the session/request scope; DetachedInstanceError; `LazyInitializationException`; queries logged during view render.
55
+ - **Why hard to find**: bug appears only after session close (background job, async response, serialization in another thread).
56
+ - **Where to look**: DRF serializers, Jinja/Django templates, Hibernate `@OneToMany(fetch = LAZY)` accessed post-session, FastAPI response models, Rails view partials.
57
+ - **Fix direction**: eager-load in the query that produced the object; or convert to plain DTO before crossing the scope boundary; SQLAlchemy `expire_on_commit=False` + explicit loads; Hibernate `JOIN FETCH`.
58
+
59
+ ### 6. Mass UPDATE / DELETE without correct WHERE
60
+ - **Signature**: `UPDATE users SET ...` with no `WHERE`, or `WHERE 1=1`, or predicate built from a possibly-empty filter list (`WHERE id IN ({ids})` where `ids` is `[]` and templates to `IN ()` or `IN (1)`).
61
+ - **Why hard to find**: tests with seeded data pass; the empty-filter branch only hits at runtime with an unexpected input.
62
+ - **Where to look**: admin actions, bulk endpoints, cleanup jobs, query builders that interpolate user-supplied lists, ORM `.update()` on an unfiltered queryset.
63
+ - **Fix direction**: refuse to execute when filter is empty; require explicit confirmation flag for full-table operations; in Django, `Model.objects.update(...)` on the manager is a known footgun — force `.filter(...)` first.
64
+
65
+ ### 7. SQL injection via string concatenation
66
+ - **Signature**: f-strings / template literals / `+` building SQL with user input; `cursor.execute(f"... {user_input}")`; `db.raw('SELECT ... ' + name)`; dynamic `ORDER BY` from query param.
67
+ - **Why hard to find**: parameterized queries elsewhere create false sense of safety; injection often hides in `ORDER BY`, `LIMIT`, table/column names where placeholders don't work.
68
+ - **Where to look**: any raw SQL helper, search endpoints, reporting/CSV exports, dynamic sort/filter, admin-only tooling (still privilege-escalation surface).
69
+ - **Fix direction**: parameterized queries everywhere; for identifiers, use an allow-list mapping, not interpolation. Cross-ref `security-auth.md`.
70
+
71
+ ### 8. Cursor / connection not closed; pool exhaustion
72
+ - **Signature**: streaming query (`cursor.execute` + iterate) without `with` / `try/finally`; long-lived sessions; connection acquired before slow work; `defer conn.Close()` missing; connection leak in error paths.
73
+ - **Why hard to find**: leak rate is small; pool drains over days, then sudden total stall.
74
+ - **Where to look**: streaming exports, batch processors, error branches that early-return without releasing, request handlers that fetch a connection then await external I/O.
75
+ - **Fix direction**: context-manager / `try-with-resources` always; acquire connection as late as possible, release as early as possible; set pool `maxLifetime` and `leakDetectionThreshold`; monitor in-use vs idle counts.
76
+
77
+ ### 9. Bulk insert without batching; non-backwards-compatible migration
78
+ - **Signature**: `for row in rows: db.insert(row)` for 100k rows; one giant transaction; `ALTER TABLE ... DROP COLUMN` or `NOT NULL` added in same release as code that still reads/writes the column.
79
+ - **Why hard to find**: works fine in staging with small datasets; in prod causes lock storms, replication lag, or hard failures during rolling deploy.
80
+ - **Where to look**: ETL/import scripts, seed jobs, `migrations/` adjacent to recent app code changes that drop/rename columns or change nullability.
81
+ - **Fix direction**: batch inserts (chunks of 500-5000), use `COPY` / `INSERT ... ON CONFLICT` / bulk APIs; for migrations follow expand-contract: add nullable column → backfill → ship code that writes both → ship code that reads new → drop old. Never drop a column in the same release as the code that stops writing it.
82
+
83
+ ## Distributed Systems Patterns
84
+
85
+ ### 10. Retry without idempotency
86
+ - **Signature**: `withRetry(() => chargeCard(...))`, `for attempt in range(3): post(...)`, queue consumer that reprocesses on exception, no `Idempotency-Key` header, no dedup table.
87
+ - **Why hard to find**: happy path is fine; only manifests when the first attempt actually succeeded but the response was lost (timeout, 502, network partition).
88
+ - **Where to look**: payment / charge / refund, email / SMS send, order placement, webhook delivery, queue consumers with at-least-once semantics, any `POST` wrapped in retry.
89
+ - **Fix direction**: caller generates an idempotency key (UUIDv4 per logical intent, NOT per HTTP attempt); server stores `(key → response)` in same transaction as the side effect; replay returns stored response. Use natural keys where possible (order ID, request ID).
90
+
91
+ ### 11. Retry storm / thundering herd
92
+ - **Signature**: fixed retry delay, no jitter, all clients retrying on the same wall-clock boundary, cron `* * * * *` firing identical work, cache-miss stampede after expiry.
93
+ - **Why hard to find**: looks like a sudden upstream outage caused by your own retries; metrics show synchronized spikes.
94
+ - **Where to look**: retry helpers without jitter, `setTimeout(retry, 1000)` style, cron-driven jobs across many instances, post-outage reconnect floods.
95
+ - **Fix direction**: exponential backoff **with full jitter** (`sleep = random(0, base * 2^attempt)`); cap max retries; circuit-break after N consecutive failures; stagger cron with random offset; for caches, use single-flight / probabilistic early refresh.
96
+
97
+ ### 12. Retry-After ignored / DOS upstream
98
+ - **Signature**: 429 / 503 response with `Retry-After` header but code retries on its own schedule; rate-limit headers (`X-RateLimit-Reset`, `RateLimit-Reset`) unread.
99
+ - **Why hard to find**: works during normal traffic; under upstream pressure, your retries amplify the outage.
100
+ - **Where to look**: HTTP client wrappers, third-party SDK calls (Stripe, Slack, GitHub, OpenAI), webhook senders.
101
+ - **Fix direction**: honor `Retry-After` (delta-seconds or HTTP-date); combine with circuit breaker; never retry faster than the server requests.
102
+
103
+ ### 13. No circuit breaker / cascade failure
104
+ - **Signature**: upstream timeout, downstream keeps calling, request queue depth climbs, threads/goroutines all blocked on the dead dependency, healthy traffic starves.
105
+ - **Why hard to find**: looks like a problem with your own service; root cause is one slow dependency.
106
+ - **Where to look**: every cross-service / cross-network call without a `CircuitBreaker`, `Polly`, `resilience4j`, `gobreaker`, or equivalent; SDKs that default to infinite retries.
107
+ - **Fix direction**: wrap remote calls in a circuit breaker (closed / open / half-open); shed load fast (fail-fast) when open; set tight per-call timeouts; bulkhead pools so one dependency can't drain all workers.
108
+
109
+ ### 14. Timeouts not propagated (deadline leak)
110
+ - **Signature**: caller has 5s budget, calls downstream with no timeout or its own 30s timeout; gRPC `context.Context` not threaded; HTTP server cancels but worker keeps running; `AbortController` never wired.
111
+ - **Why hard to find**: upstream gave up but downstream still computes/writes — orphan work, double-write, wasted resources.
112
+ - **Where to look**: service-to-service handlers, background goroutines spawned per request, async tasks fired without cancellation tokens, ORM queries with no statement timeout.
113
+ - **Fix direction**: thread the deadline (Go `context`, .NET `CancellationToken`, JS `AbortSignal`, gRPC deadlines); set DB `statement_timeout`; on cancellation, abort downstream work; budget time across the call graph.
114
+
115
+ ### 15. Clock skew assumed away
116
+ - **Signature**: ordering events by `Date.now()` from multiple machines, leases / TTLs using local clock, "is this token expired" by comparing two services' clocks, sequence numbers derived from timestamp.
117
+ - **Why hard to find**: NTP usually keeps drift small; bugs surface during NTP outages, VM live-migration, or container clock jumps.
118
+ - **Where to look**: distributed locks, leader election, audit logs ordered across services, JWT / OAuth expiry checks, dedup windows.
119
+ - **Fix direction**: use logical clocks (Lamport, vector clocks) or monotonic IDs for ordering; allow ± skew margin on expiry checks; for leases use a single source of time (the holder, or the coordinator).
120
+
121
+ ### 16. Leader election split-brain
122
+ - **Signature**: two nodes both believe they're leader during partition; fencing token absent; "I'm leader if I held the lock 5s ago" without renewal/expiry check.
123
+ - **Why hard to find**: requires a real partition or GC pause to reproduce; tests almost never cover it.
124
+ - **Where to look**: custom leader code, ZooKeeper/etcd/Consul integrations, Redis-based locks (Redlock pitfalls), cron singletons.
125
+ - **Fix direction**: use a consensus system (etcd, ZooKeeper, Consul); always pass a **fencing token** to downstream writes so the stale leader's writes are rejected; on lease loss, stop work immediately.
126
+
127
+ ### 17. At-least-once consumer without dedup
128
+ - **Signature**: SQS / Kafka / RabbitMQ consumer that processes message then commits offset; "exactly once" claimed but not implemented; no dedup table, no idempotency key on message.
129
+ - **Why hard to find**: redelivery is rare; duplicates show up as double-charge, double-email, double-record-created — often noticed only by users.
130
+ - **Where to look**: queue consumers, webhook receivers, Kafka stream processors, event-sourcing projections.
131
+ - **Fix direction**: treat all delivery as at-least-once; dedup by message ID in a unique-keyed table (commit dedup row + side effect in same transaction); or use idempotent operations (upserts, set-not-add).
132
+
133
+ ### 18. Out-of-order delivery breaking event sourcing
134
+ - **Signature**: events processed in arrival order, not in causal/sequence order; partition key not chosen to preserve per-entity order; consumer assumes monotonic version.
135
+ - **Why hard to find**: most partitions deliver in order; reorder happens on rebalance, retry, or fan-out.
136
+ - **Where to look**: Kafka consumers with multiple partitions per entity, SQS standard queues (not FIFO), pub/sub fan-out, projection rebuilds.
137
+ - **Fix direction**: partition by entity ID so per-entity order is preserved; include a sequence number / version on each event; consumer rejects stale versions; use FIFO queues where strict order matters.
138
+
139
+ ### 19. Read-your-writes failure on eventually-consistent store
140
+ - **Signature**: write to leader / primary, immediate read from replica / cache; user submits form and the new value doesn't appear; DynamoDB `ConsistentRead=false`, S3 list-after-write, read replica lag.
141
+ - **Why hard to find**: passes in dev with single node; in prod the replica lag window is just long enough for the user to refresh.
142
+ - **Where to look**: post-write redirects, "view your submission" pages, search-after-index, cache-invalidate-then-read.
143
+ - **Fix direction**: read from primary after a write within a session; use `ConsistentRead=true`; sticky-session on a read-your-writes token; or render the just-written value from the response, don't re-fetch.
144
+
145
+ ### 20. Cache stampede / dogpile
146
+ - **Signature**: TTL expires, N concurrent requests all miss, all recompute, all write back — origin gets hammered.
147
+ - **Why hard to find**: invisible until traffic crosses a threshold; logs show synchronized origin spikes at TTL boundaries.
148
+ - **Where to look**: cache wrappers (`getOrSet`, `remember`), hot-key reads (homepage, feature flags), Redis caches with sharp TTLs.
149
+ - **Fix direction**: single-flight / request coalescing (one fetch fills for all waiters); probabilistic early refresh (XFetch / "stale-while-revalidate"); jitter TTLs; for very hot keys, refresh in background. Cross-ref `concurrency-races.md`.
150
+
151
+ ### 21. Webhook receiver not idempotent
152
+ - **Signature**: webhook handler creates row / sends email per request, no dedup on event ID; provider retries on 5xx or timeout; signature verified but ID not stored.
153
+ - **Why hard to find**: providers (Stripe, GitHub, Shopify) explicitly send retries on any non-2xx or slow response; bug shows as duplicate side effects after a brief outage.
154
+ - **Where to look**: `/webhooks/...` routes, event handlers, third-party integration endpoints.
155
+ - **Fix direction**: persist event ID with unique constraint before doing work; on duplicate, return 200 with no side effect; return 2xx fast (under provider timeout) and process async if work is slow.
156
+
157
+ ### 22. "Distributed transaction" implemented as sequential calls
158
+ - **Signature**: `chargeCard(); createOrder(); sendConfirmation();` with no rollback path; "if step 3 fails we'll log it"; 2PC named but not actually two-phase; saga without compensation.
159
+ - **Why hard to find**: happy path works; partial failures leave the system inconsistent — money taken with no order, order with no notification.
160
+ - **Where to look**: orchestration code, multi-service workflows, checkout / signup flows, anything touching ≥ 2 systems.
161
+ - **Fix direction**: saga with explicit **compensating actions** for each step; transactional outbox to publish next step; idempotent steps so retry is safe; persist saga state so a restart can resume.
162
+
163
+ ### 23. Stale cache invalidation (wrong region / partial)
164
+ - **Signature**: invalidate one cache layer but not another (CDN vs origin vs in-process); invalidate one region of a multi-region cache; "wait, cache uses different key shape than read".
165
+ - **Why hard to find**: stale data shows up only to users routed to the missed region or layer; varies by geography or load balancer routing.
166
+ - **Where to look**: multi-tier cache (CDN + Redis + in-process), multi-region deployments, cache key construction code, fan-out invalidation.
167
+ - **Fix direction**: invalidate at the lowest layer that all readers share; pub/sub invalidation events across all instances/regions; cache key includes version stamp so write bumps the version (read can't see stale).
@@ -0,0 +1,197 @@
1
+ # Error Handling & Control Flow — Bug Reference
2
+
3
+ > **When to load this reference**: when scanning code that throws/catches/raises, returns
4
+ > Result/Either/Option types, uses `defer`/`finally`/`with`, calls fallible operations
5
+ > in sequence, or maintains state across operations that can fail.
6
+
7
+ Async error handling (unawaited rejections, swallowed promise errors, AbortError) lives in
8
+ `async-promises.md`. Concurrent state corruption lives in `concurrency.md`.
9
+
10
+ ## Detection signals
11
+
12
+ - Broad catches: `catch\s*\(\s*\w*\s*\)\s*\{`, `except\s*:`, `except\s+Exception`, `except\s+BaseException`, `rescue\s*$`, `catch\s*\(Throwable`, `catch\s*\(Exception\s+\w+\)`
13
+ - Silent swallows: `catch.*\{\s*\}`, `except.*:\s*pass`, `_\s*=\s*err`, `// ignore`, `# nosec`, `if err != nil \{\s*return\s*\}` (no wrap, no log)
14
+ - Bang/unwrap: `\.unwrap\(\)`, `\.expect\(`, `\.unwrap_or_else\(\|_\|`, `!!`, `as!`, `try!`, `Result\.get`, `Optional\.get\(\)`
15
+ - Panic/abort: `panic\(`, `panic!\(`, `process\.exit`, `os\.exit`, `std::process::abort`, `fatalError\(`, `assert\(false`
16
+ - Finally/defer: `finally\s*\{[^}]*return`, `finally\s*\{[^}]*throw`, `defer\s+\w+\(`, `defer\s+func\(\)`, `recover\(\)` outside `defer`
17
+ - Lost context: `raise\s+\w+\(.+\)\s*$` (no `from`), `fmt\.Errorf\(".*"\s*,\s*err\)` without `%w`, `throw new Error\(.*\.message`
18
+ - Sentinels: `return -1`, `return None #`, `return null; //`, magic numbers in error positions
19
+ - Retry: `for\s*\{`, `while\s+True:`, `while\s*\(true\)` containing fallible call without sleep/backoff/counter
20
+
21
+ ## Patterns
22
+
23
+ ### 1. Pokemon catch — "gotta catch 'em all"
24
+ - **Signature**: `try { ... } catch (_) {}`, `except: pass`, `catch (Throwable t) { log(t); }`, `except BaseException:`.
25
+ - **Why hard to find**: tests pass, logs may even show the exception, but `KeyboardInterrupt`, `SystemExit`, `OutOfMemoryError`, `AssertionError` (programming bugs) are silently absorbed alongside the I/O error you actually wanted to handle.
26
+ - **Where to look**: top-level request handlers, plugin loaders, "defensive" wrappers around third-party calls, anywhere a developer wrote "just don't crash."
27
+ - **Example**: `try: do_work() except: log("oops")` — masks a typo'd attribute access forever.
28
+ - **Fix direction**: catch the narrowest type that you have a recovery story for; let everything else propagate. If you must catch broadly at a boundary, re-raise after logging and never catch `BaseException`.
29
+ - **Language variants**: Python (`except:` or `except BaseException:`), Java (`catch (Throwable)`), JS (`catch` with no filter), Ruby (`rescue` with no class), Swift (`catch { }` without pattern).
30
+
31
+ ### 2. Broad catch + log-and-continue with broken invariants
32
+ - **Signature**: `except Exception as e: log(e); # function returns normally with half-built object`.
33
+ - **Why hard to find**: the function "succeeds," callers see no error, but the returned object is missing fields or its database row is in a tombstoned state. Bugs surface far from the cause.
34
+ - **Where to look**: constructors, builder methods, multi-field setters, ORM `save` paths with derived fields.
35
+ - **Example**: builder catches a validation error in step 3 of 5, returns the partially-populated object as if valid.
36
+ - **Fix direction**: catch → log → re-raise, OR catch → mark object invalid / roll back partial mutations before returning. Never let a caller observe a half-built result via the "happy" return path.
37
+ - **Language variants**: universal; particularly common in Python `__init__`, Go constructors that return `(T, error)` and then return `T` zero-value on err.
38
+
39
+ ### 3. `finally` overwrites the return value
40
+ - **Signature**: `try: return x finally: return y`, JS `try { return a; } finally { return b; }`, Java `try { return ok; } finally { return fallback; }`.
41
+ - **Why hard to find**: looks correct line-by-line; control flow is genuinely surprising — `finally`'s return wins.
42
+ - **Where to look**: cleanup code that "just to be safe" returns a default; transaction commit helpers.
43
+ - **Example**: function returns success even though `try` raised, because `finally` returned `False`.
44
+ - **Fix direction**: never `return` (or `break`/`continue`) from `finally`; set a variable inside try and return after.
45
+ - **Language variants**: Python, JS/TS, Java, C#. Go's `defer` cannot do this (defers don't return), but a deferred function can overwrite named returns — `defer func() { err = nil }()` is the same class of bug.
46
+
47
+ ### 4. `finally` raises and loses the original exception
48
+ - **Signature**: `finally:` block that calls `conn.close()` or `f.flush()` which itself throws.
49
+ - **Why hard to find**: traceback shows only the cleanup error; the real bug (the original exception) vanishes. Connection leaks compound it.
50
+ - **Where to look**: resource cleanup paths, `__exit__` methods, Go deferred `Close()` calls.
51
+ - **Example**: db query throws `IntegrityError`, `finally: conn.close()` raises `NameError` because conn was never assigned — only the `NameError` propagates.
52
+ - **Fix direction**: initialize resource vars to `None`/`nil` before `try`; guard cleanup with `is not None`; wrap cleanup itself in try/except and log secondary errors without raising. In Python 3, `__context__` preserves chain — verify it's actually being logged.
53
+ - **Language variants**: Python (`finally`), Java (try-with-resources mostly fixes this — flag manual `finally { close }`), Go (`defer f.Close()` discarding error), C# (`using` good, manual `finally` bad).
54
+
55
+ ### 5. Multi-step operation, no transaction, no compensation
56
+ - **Signature**: sequence of side-effecting calls (`charge_card`; `create_order`; `send_email`) with no rollback if step N fails.
57
+ - **Why hard to find**: happy path is fine; failures produce orphaned charges, ghost orders, duplicate emails. Surfaces as customer support tickets, not stack traces.
58
+ - **Where to look**: checkout flows, signup flows, anywhere two systems of record are mutated, file-system + DB combos.
59
+ - **Example**: payment succeeds, DB insert fails on unique constraint → customer charged with no order.
60
+ - **Fix direction**: wrap in a transaction where possible; otherwise use saga / outbox pattern with explicit compensation per step; make steps idempotent so retry is safe.
61
+ - **Language variants**: universal. Particularly toxic in microservice call chains.
62
+
63
+ ### 6. Confirm-after-commit with no rollback
64
+ - **Signature**: `db.commit(); send_confirmation(...)` where confirmation failure has no recovery.
65
+ - **Why hard to find**: the DB row exists, the user never gets the email/webhook, and the system has no record that the side-channel failed.
66
+ - **Where to look**: order confirmations, webhook dispatch, audit-log writes after commit.
67
+ - **Example**: order committed, SES throws; user thinks order failed and re-orders; second order also commits.
68
+ - **Fix direction**: outbox pattern — write the "needs confirmation" row in the same transaction; a separate worker drains it with retries and idempotency keys.
69
+ - **Language variants**: universal; especially common with Stripe, SES, SNS, Kafka producers.
70
+
71
+ ### 7. Re-raise loses original cause/stack
72
+ - **Signature**: Python `raise NewError("wrap")` (no `from e`); JS `throw new Error('wrap: ' + e.message)`; Java `throw new RuntimeException("wrap")` without cause arg.
73
+ - **Why hard to find**: code looks like it's "translating" errors helpfully; debugging discovers the inner stack is gone, you only see the outer frame.
74
+ - **Where to look**: exception-translation layers, repository wrappers around driver exceptions, API boundaries.
75
+ - **Example**: `except ValueError: raise AppError("bad input")` — caller has no idea which field, which line, which driver code raised.
76
+ - **Fix direction**: Python `raise AppError(...) from e`; JS `throw new Error(msg, { cause: e })`; Java `new RuntimeException(msg, e)`; Rust use `source()` chain via `thiserror`/`anyhow`.
77
+ - **Language variants**: every language with exceptions.
78
+
79
+ ### 8. Go: `return err` without wrapping
80
+ - **Signature**: `if err != nil { return err }` repeated up the stack with no context added.
81
+ - **Why hard to find**: top-level log shows `EOF` or `connection refused` with zero indication of which call site, which user, which record.
82
+ - **Where to look**: any Go function with multiple fallible calls in sequence.
83
+ - **Example**: `os.Open` deep in a tree returns plain `EOF`; caller logs "EOF" and the on-call engineer has nothing.
84
+ - **Fix direction**: `return fmt.Errorf("loading user %d: %w", id, err)` — `%w` preserves the chain for `errors.Is`/`errors.As`.
85
+ - **Language variants**: Go-specific. Rust analog: bare `?` without `.context(...)` from `anyhow`.
86
+
87
+ ### 9. Go: shadowed `err` in nested block
88
+ - **Signature**: outer `var err error` (or `err :=`), inner block uses `:=` introducing a new `err`. Outer stays `nil`.
89
+ - **Why hard to find**: code reads naturally; `go vet -vettool=shadow` catches some cases, default `go vet` does not.
90
+ - **Where to look**: `if x, err := f(); err != nil { ... }` inside a function that also has an outer `err`. Loops that capture `err` from a closure.
91
+ - **Example**: `err := outerCall(); if err == nil { v, err := innerCall(); _ = v }` — outer `err` from any later branch is unset because the inner `err` shadowed and was discarded.
92
+ - **Fix direction**: rename inner var, use `=` instead of `:=`, or run `shadow` analyzer in CI.
93
+ - **Language variants**: Go (most common). JS `let` shadowing in nested blocks is similar but usually caught by linters.
94
+
95
+ ### 10. Go: `defer` evaluates args at defer-time, not call-time
96
+ - **Signature**: `defer fmt.Println(time.Now())`, `defer log.Printf("dur=%v", time.Since(start))` where `start` was captured by value but the *computation* `time.Since(...)` is the argument.
97
+ - **Why hard to find**: looks like the deferred line will run at function exit — and the function call does — but argument expressions are evaluated immediately. `time.Now()` is the *defer* timestamp, not exit time.
98
+ - **Where to look**: timing/logging defers, defers logging mutable state.
99
+ - **Example**: `defer log.Printf("user=%v", user)` logs the user pointer at defer time; if `user` is reassigned later, the *value* logged is the early one (or for pointers, the late one — both confusing).
100
+ - **Fix direction**: wrap in a closure: `defer func() { log.Printf("dur=%v", time.Since(start)) }()`.
101
+ - **Language variants**: Go-specific. Swift `defer` evaluates at scope-exit (different semantics) — flag developers porting Go habits.
102
+
103
+ ### 11. Go: `recover()` outside the deferred function
104
+ - **Signature**: `recover()` called directly in a function body, or inside a function called *from* a `defer` (one stack frame too deep).
105
+ - **Why hard to find**: looks like it should catch panics; doesn't. Process crashes anyway.
106
+ - **Where to look**: middleware wrappers, goroutine entry points, custom panic handlers.
107
+ - **Example**: `func safe() { if r := recover(); r != nil { ... } }; defer safe()` — `recover` must be in the deferred function itself.
108
+ - **Fix direction**: call `recover` *directly* in the deferred closure: `defer func() { if r := recover(); r != nil { ... } }()`. Also: every goroutine needs its own recover; panics don't cross goroutine boundaries.
109
+ - **Language variants**: Go-specific.
110
+
111
+ ### 12. Rust: `.unwrap()` / `.expect()` on attacker-controlled input
112
+ - **Signature**: `.unwrap()` on a `parse()`, `from_str()`, header lookup, env var, JSON deserialize, or array index driven by request data.
113
+ - **Why hard to find**: unit tests pass with happy input; fuzzed or malicious input triggers panic; in a multi-threaded server one bad request can poison shared mutexes (`PoisonError`).
114
+ - **Where to look**: HTTP handlers, deserialization layers, CLI arg parsing, indexing into `Vec`/slices with user-provided offsets.
115
+ - **Example**: `let id: u64 = req.param("id").unwrap().parse().unwrap();` — any non-numeric `id` crashes the worker.
116
+ - **Fix direction**: propagate with `?`, return a typed error, or use `.ok_or(...)` / `.map_err(...)`. Reserve `expect` for invariants you can prove (and write the proof in the message).
117
+ - **Language variants**: Swift `!`/`try!`/`as!`, Kotlin `!!`, Java `Optional.get()`, JS `JSON.parse` thrown into untyped flow.
118
+
119
+ ### 13. Rust: `?` in `main` returning `Box<dyn Error>` loses detail
120
+ - **Signature**: `fn main() -> Result<(), Box<dyn Error>> { ... ? ... }` with no error formatter.
121
+ - **Why hard to find**: output is one line, no chain, no backtrace by default.
122
+ - **Where to look**: CLI tools and examples; small services that grew up.
123
+ - **Example**: a file-not-found error 6 calls deep prints as `No such file or directory (os error 2)` with no path, no operation.
124
+ - **Fix direction**: use `anyhow::Result` + `.context(...)` at boundaries; or `eyre`/`color-eyre`; enable `RUST_BACKTRACE`; print the chain via `{:?}` of `anyhow::Error`.
125
+ - **Language variants**: Rust-specific.
126
+
127
+ ### 14. JS/TS: throwing non-Error values
128
+ - **Signature**: `throw "bad input"`, `throw { code: 500 }`, `reject("nope")`, `throw 42`.
129
+ - **Why hard to find**: `instanceof Error` checks fail; `.stack`/`.message` are undefined; type-narrowed `catch (e: unknown)` code paths are skipped or crash.
130
+ - **Where to look**: legacy code, hand-rolled validators, libraries that "rejected" with strings.
131
+ - **Example**: `catch (e) { logger.error(e.message); }` logs `undefined` and you lose the original value entirely.
132
+ - **Fix direction**: always throw `Error` subclasses; if you must accept anything, normalize at catch sites with `e instanceof Error ? e : new Error(String(e))`.
133
+ - **Language variants**: JS/TS. Python `raise "x"` is a SyntaxError so this class is JS-specific.
134
+
135
+ ### 15. JS/TS: try wrapping too much — adjacent code swallowed
136
+ - **Signature**: `try { const j = JSON.parse(s); doStuff(j); render(j); } catch (e) { return null; }`.
137
+ - **Why hard to find**: developer meant to catch parse errors only; bugs in `doStuff`/`render` also return `null` and are reported as "data missing."
138
+ - **Where to look**: any `try` that wraps both an obviously-fallible call and "normal" code.
139
+ - **Example**: a `TypeError` from `doStuff(j)` returns `null`; UI shows empty state forever.
140
+ - **Fix direction**: shrink the `try` to *only* the fallible operation; handle the parsed value outside.
141
+ - **Language variants**: universal; particularly bad in JS/TS where `catch` is untyped.
142
+
143
+ ### 16. Python: `except Exception` re-raised without `from`
144
+ - **Signature**: `except Exception as e: raise CustomError("oops")` — note the missing `from e`.
145
+ - **Why hard to find**: Python 3 implicitly sets `__context__`, but explicit `from None` or assignment can suppress it; even with implicit chaining the traceback shows "During handling of the above exception, another exception occurred" which many devs mistake for noise.
146
+ - **Where to look**: framework boundaries, ORM wrappers, service-layer translation.
147
+ - **Example**: SQLAlchemy `IntegrityError` swallowed; caller gets `CustomError("oops")`; root cause invisible in Sentry filters.
148
+ - **Fix direction**: `raise CustomError("oops") from e`. Ensure your logger formats `__cause__` and `__context__`.
149
+ - **Language variants**: Python.
150
+
151
+ ### 17. Logged-but-not-propagated
152
+ - **Signature**: `try: do() except E: log.error(e)` with no `raise`, no return-value change, no retry queue, no alert.
153
+ - **Why hard to find**: log line exists somewhere; caller's contract says "succeeded"; downstream code proceeds as if the operation worked.
154
+ - **Where to look**: background jobs, scheduled tasks, "best-effort" code paths, event handlers.
155
+ - **Example**: webhook delivery fails, logged at WARN; the parent loop marks the event "processed" and moves on.
156
+ - **Fix direction**: decide explicitly — fail loudly, schedule retry, dead-letter, or document "best-effort" in the function contract. Logs are not error handling.
157
+ - **Language variants**: universal.
158
+
159
+ ### 18. Partial mutation then raise — invariant broken
160
+ - **Signature**: object mutates field A, then calls a fallible operation that throws, leaving field B unchanged. Object is now in a state the type system says is impossible.
161
+ - **Why hard to find**: subsequent reads observe the corrupted state; the function that caused it is long gone.
162
+ - **Where to look**: setters that derive multiple fields, in-place collection mutations, `__setstate__`, ORM `save` overrides, struct-update sequences.
163
+ - **Example**: `self.balance -= amount; self.ledger.append(tx)` where `append` raises (OOM, full disk) — balance debited, no record.
164
+ - **Fix direction**: compute new state into locals first, swap atomically at the end (functional-core / commit-at-end). Or use a real transaction. Or store a snapshot and restore on except.
165
+ - **Language variants**: universal; especially insidious in OOP languages with mutable objects.
166
+
167
+ ### 19. Sentinel return values silently ignored
168
+ - **Signature**: function returns `-1`, `None`, `null`, `""`, `nil`, `0` on error; callers use the value without checking.
169
+ - **Why hard to find**: type system permits it; tests with happy values pass; the `-1` flows into arithmetic, indexing, or comparisons producing wrong-but-plausible results.
170
+ - **Where to look**: C/C++ APIs returning errno-style codes, legacy Java returning `null`, JS functions returning `undefined`, Python returning `None` on "not found."
171
+ - **Example**: `int n = read(fd, buf, sz);` then loop uses `n` as length — `-1` interpreted as huge unsigned value.
172
+ - **Fix direction**: use `Result`/`Either`/`Optional`/exceptions; if stuck with sentinels, lint for unchecked return values (`-Wunused-result`, `errcheck`).
173
+ - **Language variants**: C, C++, Go (`(T, error)` pair but ignored `error`), Java, JS.
174
+
175
+ ### 20. Comparing errors by message string
176
+ - **Signature**: `if str(e) == "user not found":`, `if err.Error() == "EOF"`, `if (err.message.includes("timeout"))`.
177
+ - **Why hard to find**: works in dev; breaks when library updates wording, when locale changes (Windows + non-English), when message includes a path/id.
178
+ - **Where to look**: retry logic that classifies errors, test assertions, error-translation layers.
179
+ - **Example**: code retries when `err.Error() == "connection refused"` — driver upgrade changes wording to `"dial: connection refused"` and retries silently stop.
180
+ - **Fix direction**: use typed errors / `errors.Is` / `errors.As` (Go), exception subclasses (Python/Java), `instanceof` chains (JS), error codes (POSIX `errno`).
181
+ - **Language variants**: universal.
182
+
183
+ ### 21. Retry without max attempts or backoff
184
+ - **Signature**: `while True: try: do() except: continue`, `for { if err := f(); err != nil { continue } }`.
185
+ - **Why hard to find**: works during transient blips; on a persistent failure (auth revoked, deleted resource, schema mismatch) it becomes a tight CPU loop or self-DDoS of the dependency.
186
+ - **Where to look**: HTTP clients, database call sites, message-broker consumers, custom retry decorators.
187
+ - **Example**: token expired permanently; retry loop hammers IdP at 100k req/s and tokens are now rate-limited org-wide.
188
+ - **Fix direction**: bounded attempts + exponential backoff + jitter + circuit breaker; distinguish transient (429, 5xx, timeout) from terminal (4xx auth, 404) and never retry terminal.
189
+ - **Language variants**: universal; lean on libraries (`tenacity`, `backoff`, `cenkalti/backoff`, `tokio-retry`).
190
+
191
+ ### 22. Asserts disabled in production hide invariant checks
192
+ - **Signature**: `assert user.is_valid()`, `assert!(invariant)` used for runtime checks; Python `-O` strips them, C `NDEBUG` strips them, Rust `debug_assert!` only fires in debug.
193
+ - **Why hard to find**: dev/CI runs catch issues; production silently proceeds with invariants violated.
194
+ - **Where to look**: input validation that uses `assert`, "this can't happen" checks, security-relevant pre-conditions.
195
+ - **Example**: `assert tenant_id == request.tenant_id` — stripped in prod build; cross-tenant leak.
196
+ - **Fix direction**: use real conditionals + raise/return for runtime invariants; reserve assert for true never-can-happen sanity checks.
197
+ - **Language variants**: Python (`-O`), C/C++ (`NDEBUG`), Rust (`debug_assert!`), Swift (`assert` vs `precondition`).
@@ -0,0 +1,94 @@
1
+ # Gate-Bugs Item Template
2
+
3
+ > Used by `bug-scan` in **gate mode** to convert each finding into a substrate item under
4
+ > `.work/active/stories/` (or `.work/backlog/` for Low). Mirrors the gate-security pattern.
5
+
6
+ ## File location
7
+
8
+ - Critical / High → `.work/active/stories/<id>.md` with `stage: implementing`
9
+ - Medium → `.work/active/stories/<id>.md` with `stage: drafting`
10
+ - Low → `.work/backlog/<id>.md` (not stage-managed)
11
+
12
+ ## ID convention
13
+
14
+ `gate-bugs-<short-slug>` where slug describes the finding briefly.
15
+
16
+ Examples:
17
+ - `gate-bugs-stale-closure-cart-effect`
18
+ - `gate-bugs-tx-not-rolled-back-on-error`
19
+ - `gate-bugs-await-in-foreach-callback`
20
+ - `gate-bugs-goroutine-leak-on-blocked-send`
21
+
22
+ Keep slugs ≤ 40 chars after the prefix. If you hit the same slug twice, suffix `-2`, `-3`.
23
+
24
+ ## Frontmatter + body template
25
+
26
+ ```yaml
27
+ ---
28
+ id: gate-bugs-<short-slug>
29
+ kind: story
30
+ stage: implementing # Critical or High
31
+ # OR drafting for Medium
32
+ # OR (omit, in backlog/) for Low
33
+ tags: [bug, <domain-tag>]
34
+ parent: null
35
+ depends_on: []
36
+ release_binding: <version>
37
+ gate_origin: bugs
38
+ created: YYYY-MM-DD
39
+ updated: YYYY-MM-DD
40
+ ---
41
+
42
+ # <one-line title>
43
+
44
+ ## Severity
45
+ Critical | High | Medium | Low
46
+
47
+ ## Domain
48
+ <concurrency | async | state | resource-leak | time-numbers | error-handling | data-layer | language-footgun>
49
+
50
+ ## Pattern
51
+ <named pattern from references/<domain>.md — or "new" if not catalogued>
52
+
53
+ ## Location
54
+ `<file>:<line>`
55
+
56
+ ## Evidence
57
+ \`\`\`<lang>
58
+ <short code snippet, 1-5 lines>
59
+ \`\`\`
60
+
61
+ ## Why it's a bug
62
+ <1-2 sentences — the specific failure mode under realistic conditions>
63
+
64
+ ## Remediation direction
65
+ <what should change — direction, not a finished fix>
66
+
67
+ ## Also flagged by
68
+ <other domain(s), if this same location was flagged elsewhere; otherwise omit section>
69
+ ```
70
+
71
+ ## Domain tag mapping
72
+
73
+ | Domain | Tag |
74
+ |---|---|
75
+ | Concurrency & races | `concurrency` |
76
+ | Async / promises | `async` |
77
+ | State & closures | `state` |
78
+ | Resource leaks | `resource-leak` |
79
+ | Time & numbers | `time-numbers` |
80
+ | Error handling | `error-handling` |
81
+ | Data layer | `data-layer` |
82
+ | Language footguns | `language-footgun` |
83
+
84
+ All items also carry the umbrella `bug` tag, so the substrate can grep "all bug findings"
85
+ in one filter.
86
+
87
+ ## Rules
88
+
89
+ - One finding = one item. Do not bundle. Even if two findings share a file, separate them.
90
+ - `release_binding` must match the release version that invoked the gate.
91
+ - `gate_origin: bugs` is required — that's how `release-deploy` identifies these items as
92
+ gate output and how re-runs detect duplicates.
93
+ - Set `created` and `updated` to today's ISO date.
94
+ - After creating all items, commit them in a single commit: `gate-bugs: <N> findings for <version>`.