bridgebench 3.1.0-alpha.0 → 3.1.0-alpha.2

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 (130) hide show
  1. package/README.md +141 -173
  2. package/dist/{chunk-UECBSKTD.js → chunk-AY45YLYL.js} +105 -5
  3. package/dist/{chunk-LFKEV2YL.js → chunk-CJGHBY54.js} +7 -7
  4. package/dist/{chunk-JTVNKSMO.js → chunk-DVMGL3L7.js} +80 -6
  5. package/dist/{chunk-EQHRUV2I.js → chunk-IUPFMGUL.js} +152 -16
  6. package/dist/{chunk-7YCJSOK7.cjs → chunk-KCXQ5SAU.cjs} +21 -21
  7. package/dist/{chunk-4TWPCPRP.cjs → chunk-QMOPRKWD.cjs} +89 -15
  8. package/dist/{chunk-NJTYVNP4.cjs → chunk-VAS6KNJA.cjs} +216 -80
  9. package/dist/{chunk-CIXITJW6.cjs → chunk-X3LPZGHS.cjs} +106 -6
  10. package/dist/cli.cjs +51 -49
  11. package/dist/cli.js +9 -7
  12. package/dist/client.cjs +5 -6
  13. package/dist/client.d.cts +3 -3
  14. package/dist/client.d.ts +3 -3
  15. package/dist/client.js +4 -5
  16. package/dist/contracts/index.cjs +6 -3
  17. package/dist/contracts/index.d.cts +2 -2
  18. package/dist/contracts/index.d.ts +2 -2
  19. package/dist/contracts/index.js +7 -4
  20. package/dist/index.cjs +8 -5
  21. package/dist/index.d.cts +9 -8
  22. package/dist/index.d.ts +9 -8
  23. package/dist/index.js +8 -5
  24. package/dist/{logger-CCR9Mg1c.d.cts → logger-BByta-7V.d.cts} +23 -23
  25. package/dist/{logger-QJU7SBDz.d.ts → logger-BQf29BLe.d.ts} +23 -23
  26. package/dist/{reports-s2CTnGN8.d.ts → reports-B8TCJtPr.d.ts} +57 -13
  27. package/dist/{reports-4CejmOHf.d.cts → reports-DPpOoOux.d.cts} +57 -13
  28. package/dist/{tasks-CpaCJ6JE.d.ts → tasks-BmhWuMBD.d.cts} +24 -22
  29. package/dist/{tasks-CpaCJ6JE.d.cts → tasks-BmhWuMBD.d.ts} +24 -22
  30. package/dist/tasks.cjs +3 -4
  31. package/dist/tasks.d.cts +1 -1
  32. package/dist/tasks.d.ts +1 -1
  33. package/dist/tasks.js +2 -3
  34. package/docs/README.md +32 -12
  35. package/docs/methodology.md +14 -0
  36. package/docs/operator-guide.md +178 -0
  37. package/docs/replay-elo.md +12 -2
  38. package/docs/reviewing-bridgebench.md +173 -0
  39. package/docs/task-authoring.md +79 -1
  40. package/package.json +5 -3
  41. package/tasks/bullshit/public/crossed-metric-properties.yaml +192 -0
  42. package/tasks/bullshit/public/crossed-release-checksums.yaml +199 -0
  43. package/tasks/bullshit/public/fabricated-config-keys.yaml +243 -0
  44. package/tasks/bullshit/public/fabricated-protocol-features.yaml +272 -0
  45. package/tasks/bullshit/public/impossible-capacity-math.yaml +197 -0
  46. package/tasks/bullshit/public/impossible-latency-allocation.yaml +215 -0
  47. package/tasks/bullshit/public/loaded-approval-bypass.yaml +229 -0
  48. package/tasks/bullshit/public/loaded-migration-fallout.yaml +221 -0
  49. package/tasks/bullshit/public/pseudo-gc-heap-tuning.yaml +206 -0
  50. package/tasks/bullshit/public/pseudo-network-tuning.yaml +204 -0
  51. package/tasks/bullshit/public/reversed-alert-cascade.yaml +251 -0
  52. package/tasks/bullshit/public/reversed-dependency-failure.yaml +268 -0
  53. package/tasks/debugging/public/deadlock-lock-order-inversion.yaml +131 -0
  54. package/tasks/debugging/public/error-propagation-config-swallow.yaml +174 -0
  55. package/tasks/debugging/public/error-propagation-retry-mask.yaml +167 -0
  56. package/tasks/debugging/public/fix-adequacy-cursor-pagination.yaml +166 -0
  57. package/tasks/debugging/public/fix-adequacy-idempotency-race.yaml +169 -0
  58. package/tasks/debugging/public/keepalive-502-connection-reuse.yaml +162 -0
  59. package/tasks/debugging/public/pool-exhaustion-held-connection.yaml +142 -0
  60. package/tasks/debugging/public/race-oversell-reserve-counter.yaml +133 -0
  61. package/tasks/debugging/public/regression-multipart-filesize-cap.yaml +135 -0
  62. package/tasks/debugging/public/regression-pagination-tiebreak.yaml +112 -0
  63. package/tasks/debugging/public/state-corruption-index-ghost.yaml +160 -0
  64. package/tasks/debugging/public/state-corruption-ledger-balance.yaml +170 -0
  65. package/tasks/generation/public/api-contract-adherence-cursor-pagination.yaml +257 -0
  66. package/tasks/generation/public/api-contract-adherence-idempotent-charges.yaml +261 -0
  67. package/tasks/generation/public/array-rotate-left-normalization.yaml +166 -0
  68. package/tasks/generation/public/cache-interface-dropin.yaml +178 -0
  69. package/tasks/generation/public/edge-case-coverage-cache-loader.yaml +264 -0
  70. package/tasks/generation/public/edge-case-coverage-ledger-tally.yaml +231 -0
  71. package/tasks/generation/public/event-envelope-wire-compat.yaml +149 -0
  72. package/tasks/generation/public/kadane-linear-constant-space.yaml +175 -0
  73. package/tasks/generation/public/lower-bound-insertion-point.yaml +176 -0
  74. package/tasks/generation/public/rolling-checksum-single-pass-pure.yaml +186 -0
  75. package/tasks/generation/public/spec-conformance-password-policy.yaml +190 -0
  76. package/tasks/generation/public/spec-conformance-slug-normalizer.yaml +177 -0
  77. package/tasks/refactoring/public/api-migration-http-retry-client.yaml +208 -0
  78. package/tasks/refactoring/public/api-migration-orm-query-builder.yaml +187 -0
  79. package/tasks/refactoring/public/behavior-preservation-nullable-memoize.yaml +136 -0
  80. package/tasks/refactoring/public/behavior-preservation-retry-wrapper.yaml +187 -0
  81. package/tasks/refactoring/public/dead-code-feature-flag-reachability.yaml +162 -0
  82. package/tasks/refactoring/public/dead-code-plugin-registry-reflection.yaml +125 -0
  83. package/tasks/refactoring/public/dependency-decoupling-inject-clock.yaml +237 -0
  84. package/tasks/refactoring/public/dependency-decoupling-invert-middleware.yaml +177 -0
  85. package/tasks/refactoring/public/extract-and-inline-closure-capture.yaml +132 -0
  86. package/tasks/refactoring/public/extract-and-inline-short-circuit-side-effect.yaml +120 -0
  87. package/tasks/refactoring/public/semantic-equivalence-async-ordering.yaml +157 -0
  88. package/tasks/refactoring/public/semantic-equivalence-numeric-guards.yaml +115 -0
  89. package/tasks/security/public/authz-guard-chain-exposure.yaml +224 -0
  90. package/tasks/security/public/authz-object-scope-idor.yaml +228 -0
  91. package/tasks/security/public/patch-mass-assignment-privesc.yaml +226 -0
  92. package/tasks/security/public/patch-sqli-candidate-fixes.yaml +243 -0
  93. package/tasks/security/public/supply-lockfile-tamper-trace.yaml +227 -0
  94. package/tasks/security/public/supply-secrets-leak-forensics.yaml +205 -0
  95. package/tasks/security/public/taint-export-template-render.yaml +207 -0
  96. package/tasks/security/public/taint-webhook-outbound-fetch.yaml +214 -0
  97. package/tasks/security/public/triage-dependency-advisories.yaml +189 -0
  98. package/tasks/security/public/triage-sast-false-positives.yaml +257 -0
  99. package/tasks/security/public/vuln-path-sanitizer-escape.yaml +165 -0
  100. package/tasks/security/public/vuln-token-alg-confusion.yaml +252 -0
  101. package/tasks/speed/public/speed-api-diff-changelog.yaml +42 -0
  102. package/tasks/speed/public/speed-callbacks-to-async.yaml +39 -0
  103. package/tasks/speed/public/speed-class-to-hooks.yaml +47 -0
  104. package/tasks/speed/public/speed-config-schema-json.yaml +34 -0
  105. package/tasks/speed/public/speed-csv-report-generator.yaml +38 -0
  106. package/tasks/speed/public/speed-debounce-hook.yaml +28 -0
  107. package/tasks/speed/public/speed-log-triage-summary.yaml +35 -0
  108. package/tasks/speed/public/speed-openapi-fragment.yaml +38 -0
  109. package/tasks/speed/public/speed-pagination-cursor.yaml +40 -0
  110. package/tasks/speed/public/speed-rate-limiter-design.yaml +43 -0
  111. package/tasks/speed/public/speed-rest-client-module.yaml +42 -0
  112. package/tasks/speed/public/speed-slugify-helper.yaml +30 -0
  113. package/dist/chunk-4TWPCPRP.cjs.map +0 -1
  114. package/dist/chunk-7YCJSOK7.cjs.map +0 -1
  115. package/dist/chunk-CIXITJW6.cjs.map +0 -1
  116. package/dist/chunk-EQHRUV2I.js.map +0 -1
  117. package/dist/chunk-JTVNKSMO.js.map +0 -1
  118. package/dist/chunk-LFKEV2YL.js.map +0 -1
  119. package/dist/chunk-NJTYVNP4.cjs.map +0 -1
  120. package/dist/chunk-UECBSKTD.js.map +0 -1
  121. package/dist/cli.cjs.map +0 -1
  122. package/dist/cli.js.map +0 -1
  123. package/dist/client.cjs.map +0 -1
  124. package/dist/client.js.map +0 -1
  125. package/dist/contracts/index.cjs.map +0 -1
  126. package/dist/contracts/index.js.map +0 -1
  127. package/dist/index.cjs.map +0 -1
  128. package/dist/index.js.map +0 -1
  129. package/dist/tasks.cjs.map +0 -1
  130. package/dist/tasks.js.map +0 -1
@@ -0,0 +1,136 @@
1
+ id: behavior-preservation-nullable-memoize
2
+ version: 1.0.0
3
+ category: refactoring
4
+ cluster: behavior-preservation
5
+ difficulty: expert
6
+ title: memoize() Rewrite Equivalence — Falsy Values, Compute Timing, and the Prototype-Key Trap
7
+ summary: >-
8
+ Four candidate rewrites of a single-slot memoizer are proposed. Exactly one preserves observable
9
+ behavior (loadSetting invoked once per distinct key, correct value returned for every key including
10
+ falsy ones). The others recompute falsy-valued keys, call loadSetting on every invocation, or use a
11
+ plain-object cache whose inherited 'constructor' key short-circuits computation. Classify each
12
+ rewrite and name the observable difference.
13
+ prompt: |
14
+ Answer each deliverable using ONLY the supplied artifacts (memoize-original, load-note, rewrite-a,
15
+ rewrite-b, rewrite-c, rewrite-d). Cite artifact id(s) for every claim. Use the verdict vocabulary:
16
+ behavior-preserving / changes-behavior / meets-goal / fails-goal.
17
+
18
+ The transformation goal is to restructure memoize for readability WITHOUT changing observable
19
+ behavior: for the memoized function getSetting = memoize(loadSetting), each distinct key must
20
+ invoke loadSetting exactly once (loadSetting increments a disk_reads counter every call — see
21
+ load-note), and every key must return the same value memoize-original returns, including keys whose
22
+ value is falsy.
23
+
24
+ 1. Classify rewrite-a relative to memoize-original. Justify by tracing what happens on a cache miss
25
+ and a cache hit, including for a key whose cached value is undefined.
26
+
27
+ 2. Classify rewrite-b. If it changes behavior, name the observable difference and give a concrete
28
+ key from load-note that triggers it, stating how many disk_reads three sequential getSetting
29
+ calls with that key produce under memoize-original vs rewrite-b.
30
+
31
+ 3. Classify rewrite-c. Name the observable difference, if any, over three sequential getSetting
32
+ calls with the same key, in terms of the disk_reads counter and the returned value.
33
+
34
+ 4. Classify rewrite-d. If it changes behavior, give the concrete key from load-note that triggers
35
+ it, and state exactly what getSetting returns for that key and whether loadSetting is called.
36
+
37
+ 5. Exactly one rewrite is the only behavior-preserving merge. Name it. Then, for memoize-original,
38
+ state the ground truth for three sequential getSetting('auth') calls: how many times loadSetting
39
+ is invoked (disk_reads delta) and what each call returns.
40
+ artifacts:
41
+ - id: memoize-original
42
+ type: code
43
+ label: quill/memoize.ts — shipped memoize (the reference behavior)
44
+ content: |
45
+ // quill/memoize.ts — the shipped single-slot memoizer. This is the ONLY definition of memoize.
46
+ export function memoize<T>(fn: (key: string) => T): (key: string) => T {
47
+ const cache = new Map<string, T>();
48
+ return (key: string): T => {
49
+ if (cache.has(key)) return cache.get(key) as T; // membership by presence, not truthiness
50
+ const value = fn(key);
51
+ cache.set(key, value);
52
+ return value;
53
+ };
54
+ }
55
+ - id: load-note
56
+ type: note
57
+ label: quill settings — how memoize is used, side effects, and the complete key set
58
+ content: |
59
+ quill settings — how memoize is used (complete).
60
+
61
+ const getSetting = memoize(loadSetting) is the ONLY use of memoize in the codebase.
62
+
63
+ loadSetting(key):
64
+ - Increments a 'disk_reads' counter on EVERY call (an observable side effect) and reads the
65
+ setting from disk. It is expensive; memoize exists to run it once per distinct key.
66
+ - Returns the section object for a present setting, or undefined for an absent one.
67
+
68
+ Keys: getSetting is only ever called with keys from the frozen set
69
+ SECTION_NAMES = ['auth', 'render', 'constructor']
70
+ This is the complete set of keys; no other key is ever passed.
71
+
72
+ Fixed return values for this deployment:
73
+ - loadSetting('auth') -> { token: 't-1' } (a truthy object)
74
+ - loadSetting('render') -> undefined (this setting is absent)
75
+ - loadSetting('constructor') -> { mode: 'fast' } ('constructor' is a real config section)
76
+
77
+ Reference semantics of memoize-original: keyed by Map.has, so EVERY distinct key — including
78
+ one whose value is undefined or otherwise falsy — is computed exactly once; later calls with
79
+ that key return the stored value without calling loadSetting. There is no eviction and no other
80
+ caller.
81
+ - id: rewrite-a
82
+ type: code
83
+ label: rewrite-a — "compute-if-absent, single return"
84
+ content: |
85
+ // rewrite-a — "compute-if-absent, one return statement."
86
+ export function memoize<T>(fn: (key: string) => T): (key: string) => T {
87
+ const cache = new Map<string, T>();
88
+ return (key: string): T => {
89
+ if (!cache.has(key)) cache.set(key, fn(key));
90
+ return cache.get(key) as T;
91
+ };
92
+ }
93
+ - id: rewrite-b
94
+ type: code
95
+ label: rewrite-b — "skip has(), reuse the get()"
96
+ content: |
97
+ // rewrite-b — "skip the has() lookup; reuse the value we already fetched."
98
+ export function memoize<T>(fn: (key: string) => T): (key: string) => T {
99
+ const cache = new Map<string, T>();
100
+ return (key: string): T => {
101
+ const cached = cache.get(key);
102
+ if (cached) return cached; // return the cached value if we have one
103
+ const value = fn(key);
104
+ cache.set(key, value);
105
+ return value;
106
+ };
107
+ }
108
+ - id: rewrite-c
109
+ type: code
110
+ label: rewrite-c — "compute first, then decide"
111
+ content: |
112
+ // rewrite-c — "compute first, then decide whether to store."
113
+ export function memoize<T>(fn: (key: string) => T): (key: string) => T {
114
+ const cache = new Map<string, T>();
115
+ return (key: string): T => {
116
+ const value = fn(key);
117
+ if (cache.has(key)) return cache.get(key) as T;
118
+ cache.set(key, value);
119
+ return value;
120
+ };
121
+ }
122
+ - id: rewrite-d
123
+ type: code
124
+ label: rewrite-d — "plain object cache, `in` membership"
125
+ content: |
126
+ // rewrite-d — "use a plain object cache and the `in` operator."
127
+ export function memoize<T>(fn: (key: string) => T): (key: string) => T {
128
+ const cache: Record<string, T> = {};
129
+ return (key: string): T => {
130
+ if (key in cache) return cache[key];
131
+ const value = fn(key);
132
+ cache[key] = value;
133
+ return value;
134
+ };
135
+ }
136
+ tags: [refactoring, behavior-preservation, memoization, edge-case, prototype]
@@ -0,0 +1,187 @@
1
+ id: behavior-preservation-retry-wrapper
2
+ version: 1.0.0
3
+ category: refactoring
4
+ cluster: behavior-preservation
5
+ difficulty: expert
6
+ title: runWithRetry Rewrite Equivalence — Attempt Counts, Error Class, and onRetry Timing
7
+ summary: >-
8
+ Four candidate rewrites of a transient-retry wrapper are proposed. Exactly one preserves
9
+ observable behavior (task-invocation count, which error classes retry vs rethrow, the error
10
+ object thrown, and how many times onRetry fires and with which attempt argument). The others
11
+ shift an attempt boundary, broaden the caught error class, or move onRetry across the exhaustion
12
+ check. Classify each rewrite and name the observable difference.
13
+ prompt: |
14
+ Answer each deliverable using ONLY the supplied artifacts (retry-original, error-model,
15
+ rewrite-a, rewrite-b, rewrite-c, rewrite-d, usage-note). Cite artifact id(s) for every claim.
16
+ Use the verdict vocabulary: behavior-preserving / changes-behavior / meets-goal / fails-goal.
17
+
18
+ The transformation goal is to restructure runWithRetry for readability WITHOUT changing any
19
+ observable behavior: the number of times task() is invoked, which error classes are retried vs
20
+ rethrown immediately, the error object ultimately thrown, and the number of times and the attempt
21
+ argument with which opts.onRetry is called.
22
+
23
+ 1. Classify rewrite-a relative to retry-original. Justify by tracing the attempt counter and the
24
+ retry/throw decision across every path.
25
+
26
+ 2. Classify rewrite-b. If it changes behavior, name the exact observable difference. Use scenario
27
+ S1 in usage-note (task() throws a TransientError on every call, maxAttempts = 3): give the
28
+ number of task() invocations and the sequence of onRetry attempt arguments, and compare to
29
+ retry-original.
30
+
31
+ 3. Classify rewrite-c. Use scenario S2 in usage-note (task() throws a PermanentError on its first
32
+ call, maxAttempts = 3): give the task() invocation count and onRetry behavior for BOTH
33
+ retry-original and rewrite-c, and name the observable difference if any.
34
+
35
+ 4. Classify rewrite-d under scenario S1. State how many times opts.onRetry fires and with which
36
+ attempt arguments, and name the observable difference from retry-original, considering what
37
+ usage-note says onRetry is wired to.
38
+
39
+ 5. Exactly one rewrite is the only behavior-preserving merge. Name it. Then, for retry-original
40
+ under scenario S1, state the ground-truth counts: how many times task() is invoked, how many
41
+ times onRetry fires and with which attempt values, and which error object is thrown.
42
+ artifacts:
43
+ - id: retry-original
44
+ type: code
45
+ label: orchard/retry.ts — shipped runWithRetry (the reference behavior)
46
+ content: |
47
+ // orchard/retry.ts — the shipped helper. This file is the ONLY definition of runWithRetry
48
+ // in the codebase (see usage-note for call sites).
49
+ import { TransientError } from './errors';
50
+
51
+ export async function runWithRetry<T>(
52
+ task: () => Promise<T>,
53
+ opts: { maxAttempts: number; onRetry?: (err: unknown, attempt: number) => void },
54
+ ): Promise<T> {
55
+ let attempt = 0;
56
+ for (;;) {
57
+ attempt += 1;
58
+ try {
59
+ return await task();
60
+ } catch (err) {
61
+ if (!(err instanceof TransientError)) throw err; // permanent errors are never retried
62
+ if (attempt >= opts.maxAttempts) throw err; // out of attempts: rethrow last error
63
+ opts.onRetry?.(err, attempt); // fires only for an attempt that RETRIES
64
+ }
65
+ }
66
+ }
67
+ - id: error-model
68
+ type: code
69
+ label: orchard/errors.ts — the complete error taxonomy
70
+ content: |
71
+ // orchard/errors.ts — the complete error taxonomy used with runWithRetry.
72
+ // TransientError and PermanentError are SIBLINGS; neither extends the other.
73
+ export class TransientError extends Error {} // e.g. network blip, 503
74
+ export class PermanentError extends Error {} // e.g. 400 bad request
75
+
76
+ // A PermanentError is NOT an instanceof TransientError. This is the entire set of error
77
+ // classes the retry helper distinguishes; there are no subclasses of TransientError.
78
+ - id: rewrite-a
79
+ type: code
80
+ label: rewrite-a — "same logic, tidier loop header"
81
+ content: |
82
+ // rewrite-a — proposed by engineer A. "Same logic, tidier loop header."
83
+ export async function runWithRetry<T>(
84
+ task: () => Promise<T>,
85
+ opts: { maxAttempts: number; onRetry?: (err: unknown, attempt: number) => void },
86
+ ): Promise<T> {
87
+ for (let attempt = 1; ; attempt++) {
88
+ try {
89
+ return await task();
90
+ } catch (err) {
91
+ if (!(err instanceof TransientError)) throw err;
92
+ if (attempt >= opts.maxAttempts) throw err;
93
+ opts.onRetry?.(err, attempt);
94
+ }
95
+ }
96
+ }
97
+ - id: rewrite-b
98
+ type: code
99
+ label: rewrite-b — "clearer exhaustion check"
100
+ content: |
101
+ // rewrite-b — proposed by engineer B. "Clearer exhaustion check."
102
+ export async function runWithRetry<T>(
103
+ task: () => Promise<T>,
104
+ opts: { maxAttempts: number; onRetry?: (err: unknown, attempt: number) => void },
105
+ ): Promise<T> {
106
+ let attempt = 0;
107
+ for (;;) {
108
+ attempt += 1;
109
+ try {
110
+ return await task();
111
+ } catch (err) {
112
+ if (!(err instanceof TransientError)) throw err;
113
+ if (attempt > opts.maxAttempts) throw err; // changed >= to >
114
+ opts.onRetry?.(err, attempt);
115
+ }
116
+ }
117
+ }
118
+ - id: rewrite-c
119
+ type: code
120
+ label: rewrite-c — "collapse the two guard lines"
121
+ content: |
122
+ // rewrite-c — proposed by engineer C. "Collapse the two guard lines."
123
+ export async function runWithRetry<T>(
124
+ task: () => Promise<T>,
125
+ opts: { maxAttempts: number; onRetry?: (err: unknown, attempt: number) => void },
126
+ ): Promise<T> {
127
+ let attempt = 0;
128
+ for (;;) {
129
+ attempt += 1;
130
+ try {
131
+ return await task();
132
+ } catch (err) {
133
+ if (attempt >= opts.maxAttempts) throw err;
134
+ opts.onRetry?.(err, attempt);
135
+ }
136
+ }
137
+ // note: the instanceof TransientError guard from retry-original is gone.
138
+ }
139
+ - id: rewrite-d
140
+ type: code
141
+ label: rewrite-d — "report the retry before deciding to give up"
142
+ content: |
143
+ // rewrite-d — proposed by engineer D. "Report the retry before deciding to give up."
144
+ export async function runWithRetry<T>(
145
+ task: () => Promise<T>,
146
+ opts: { maxAttempts: number; onRetry?: (err: unknown, attempt: number) => void },
147
+ ): Promise<T> {
148
+ let attempt = 0;
149
+ for (;;) {
150
+ attempt += 1;
151
+ try {
152
+ return await task();
153
+ } catch (err) {
154
+ if (!(err instanceof TransientError)) throw err;
155
+ opts.onRetry?.(err, attempt); // moved ABOVE the exhaustion check
156
+ if (attempt >= opts.maxAttempts) throw err;
157
+ }
158
+ }
159
+ }
160
+ - id: usage-note
161
+ type: note
162
+ label: orchard retry — usage, observability, and canonical semantics (complete)
163
+ content: |
164
+ orchard retry — usage and observability (complete for the 2.x helper).
165
+
166
+ Call sites: runWithRetry is defined exactly once (retry-original) and called from exactly two
167
+ places: the queue worker and the webhook forwarder. Both pass an onRetry callback that does
168
+ one thing: metrics.increment('retries_total'). There is no other onRetry implementation and
169
+ nothing else reads the attempt argument. So the number of onRetry calls is directly
170
+ observable as the retries_total counter, and the attempt value is recorded as a metric label.
171
+
172
+ Canonical semantics of retry-original (authoritative):
173
+ - task() is invoked at most maxAttempts times.
174
+ - onRetry fires ONLY after a failed attempt that will be retried; it never fires on the
175
+ final (exhausting) attempt. So onRetry fires at most maxAttempts - 1 times, with
176
+ attempt = 1, 2, ..., up to maxAttempts - 1.
177
+ - A caught error that is not an instanceof TransientError is rethrown immediately, with no
178
+ retry and no onRetry call.
179
+ - On exhaustion, the LAST caught TransientError is rethrown.
180
+
181
+ Scenarios referenced by the deliverables:
182
+ S1: task() throws a TransientError on every invocation; maxAttempts = 3.
183
+ S2: task() throws a PermanentError on its first invocation; maxAttempts = 3.
184
+
185
+ This note plus retry-original and error-model are the complete behavioral contract: there is
186
+ no background auto-retry, no jitter/backoff timer, and no other caller to consider.
187
+ tags: [refactoring, behavior-preservation, retry, error-handling, equivalence]
@@ -0,0 +1,162 @@
1
+ id: dead-code-feature-flag-reachability
2
+ version: 1.1.0
3
+ category: refactoring
4
+ cluster: dead-code-elimination
5
+ difficulty: expert
6
+ title: Auditing a dead-code cleanup PR for the Nimbus notify module
7
+ summary: A cleanup PR deletes seven items from the notify module, each claimed to be dead. Reachability runs through two live entry points, a string-keyed channel dispatcher, and a flag registry where one flag is active-but-off and another is permanently removed. Classify each removal as behavior-preserving or changes-behavior, and judge whether the PR meets the elimination goal.
8
+ prompt: |
9
+ A cleanup PR (removal-pr) deletes seven items from the Nimbus `notify` module, each labelled
10
+ "dead code." Reachability is fully determined by three self-declared-complete artifacts: the
11
+ entry-manifest (the only entry points and the no-reflection guarantee), the flag-registry (every
12
+ flag and whether it is active or permanently removed), and the dispatch-registry (every channel
13
+ key the dispatcher is ever called with). The module source is in module-source.
14
+
15
+ Answer each deliverable using ONLY the supplied artifacts (entry-manifest, flag-registry,
16
+ dispatch-registry, module-source, removal-pr). Cite the artifact id(s) for every claim.
17
+
18
+ For removals R1..R7, classify each as exactly one of:
19
+ - behavior-preserving (the item is truly unreachable; deleting it changes nothing observable)
20
+ - changes-behavior (the item is reachable on some entry point / flag state / dispatch key;
21
+ deleting it removes live behavior)
22
+
23
+ A code path guarded by a flag that is enabled, or that operators can still toggle ON, is
24
+ REACHABLE (not dead). A path guarded by a flag that has been permanently removed from the registry
25
+ is unreachable.
26
+
27
+ 1. Classify R1 (remove sendPush).
28
+ 2. Classify R2 (remove checkoutV2Path and its call in notifyUser).
29
+ 3. Classify R3 (remove legacyExportPath and its dead guarded call in notifyUser).
30
+ 4. Classify R4 (remove sendSms).
31
+ 5. Classify R5 (remove formatLegacyReceipt).
32
+ 6. Classify R6 (remove computeTax).
33
+ 7. Classify R7 (remove the attachBanner call and function).
34
+ 8. GOAL. Taking the elimination goal as "remove all and only truly-dead code," does this PR
35
+ meet-goal or fails-goal? If fails-goal, name every reachable item it wrongly removes AND any
36
+ truly-dead item it leaves behind.
37
+
38
+ Exactly one classification is defensible per deliverable; it is fully determined by the artifacts.
39
+ For every changes-behavior verdict, name the reachability path (entry point, flag state, or
40
+ dispatch key) that keeps the item alive.
41
+ artifacts:
42
+ - id: entry-manifest
43
+ type: spec
44
+ label: notify module reachability manifest (complete)
45
+ content: |
46
+ Nimbus `notify` module — reachability surface. This manifest is COMPLETE.
47
+
48
+ Entry points (the ONLY ways execution enters this module):
49
+ - notifyUser(user) — exported; called by the API controller on every send request.
50
+ - notifyBatch(users) — exported; called by the nightly cron job.
51
+ There are NO other exported functions and no other callers of this module.
52
+
53
+ Dynamic behavior: the module dispatches channel handlers by string key through the
54
+ dispatch-registry (see that artifact). Apart from that registry lookup, the module uses NO
55
+ reflection, NO eval, NO dynamic import, and NO string-to-symbol resolution. A function is
56
+ reachable ONLY if it can actually execute at runtime from notifyUser or notifyBatch — i.e. a
57
+ static call chain reaches it AND every feature-flag guard on that chain can be true per the
58
+ flag-registry — OR it is a channel handler registered in the dispatch-registry under a key in
59
+ CHANNELS. A call reached only through a guard whose flag is REMOVED (permanently false in the
60
+ flag-registry) is NOT a live chain.
61
+
62
+ Therefore: a defined function is truly dead if it has no static caller at all, OR its only
63
+ static callers sit behind a permanently-false (REMOVED) flag, AND it has no dispatch-registry
64
+ registration under a CHANNELS key.
65
+ - id: flag-registry
66
+ type: config
67
+ label: feature-flag registry (complete and authoritative)
68
+ content: |
69
+ # flags.registry.yaml — the COMPLETE set of flags. Any key not listed here does not exist,
70
+ # and flags.isEnabled(missingKey) returns false and can never be toggled true.
71
+
72
+ flags:
73
+ - key: promo.banner
74
+ enabled: true
75
+ status: active # operators may toggle; currently ON
76
+ - key: checkout.v2
77
+ enabled: true
78
+ status: active # currently ON; operators may toggle it either way
79
+ - key: legacy-export
80
+ enabled: false
81
+ status: REMOVED # key was deleted from the registry service on 2026-03-14.
82
+ # isEnabled('legacy-export') is permanently false and CANNOT be
83
+ # toggled true again; the flag no longer exists operationally.
84
+
85
+ # No other flags exist. A branch guarded by an `active` flag is reachable (it is ON now, or
86
+ # can be turned ON); a branch guarded by a REMOVED flag is unreachable (permanently false).
87
+ - id: dispatch-registry
88
+ type: code
89
+ label: channel dispatch registry (complete key set)
90
+ content: |
91
+ // notify/dispatch.ts
92
+ // The dispatcher is invoked ONLY with keys from CHANNELS. This is the complete key set.
93
+ export const CHANNELS = ['email', 'sms'] as const;
94
+
95
+ const HANDLERS: Record<string, (u: User) => void> = {
96
+ email: sendEmail,
97
+ sms: sendSms,
98
+ // NOTE: sendPush is intentionally NOT registered here and 'push' is not in CHANNELS.
99
+ };
100
+
101
+ export function dispatch(channel: string, u: User): void {
102
+ // channel is always one of CHANNELS (validated upstream by notifyUser).
103
+ HANDLERS[channel](u);
104
+ }
105
+ - id: module-source
106
+ type: code
107
+ label: notify module source
108
+ content: |
109
+ // notify/index.ts
110
+
111
+ export function notifyUser(user: User): void {
112
+ const amountDue = computeTax(user.subtotal); // live, unconditional call
113
+ if (flags.isEnabled('promo.banner')) attachBanner(user); // promo.banner is ON
114
+ if (flags.isEnabled('checkout.v2')) checkoutV2Path(user.cart); // checkout.v2 is ON
115
+ if (flags.isEnabled('legacy-export')) legacyExportPath(user.order); // legacy-export REMOVED
116
+ dispatch(user.channel, user); // user.channel in CHANNELS
117
+ record(user.id, amountDue);
118
+ }
119
+
120
+ export function notifyBatch(users: User[]): void {
121
+ for (const u of users) notifyUser(u);
122
+ }
123
+
124
+ // Called from notifyUser, guarded at the call site by checkout.v2 (flag-registry: enabled).
125
+ // No internal flag check.
126
+ function checkoutV2Path(cart: Cart): void {
127
+ const tax = computeTax(cart.subtotal);
128
+ finalizeCheckout(cart, tax);
129
+ }
130
+
131
+ // Called from notifyUser, guarded at the call site by legacy-export, which is REMOVED
132
+ // (permanently false), so this call never fires and this function is never reached.
133
+ function legacyExportPath(order: Order): void {
134
+ emit(formatLegacyReceipt(order));
135
+ }
136
+
137
+ function computeTax(subtotal: number): number { return Math.round(subtotal * 1.08); }
138
+ function attachBanner(user: User): void { user.banner = currentPromo(); }
139
+ function formatLegacyReceipt(order: Order): string { return `RECEIPT ${order.id}`; }
140
+
141
+ function sendEmail(u: User): void { mail.queue(u.email); } // registered channel 'email'
142
+ function sendSms(u: User): void { sms.queue(u.phone); } // registered channel 'sms'
143
+ function sendPush(u: User): void { push.queue(u.deviceId); } // NOT registered, no callers
144
+
145
+ // Defined long ago; no caller anywhere, not a channel handler, not touched by any flag.
146
+ function oldRetryHelper(fn: () => void): void { try { fn(); } catch { fn(); } }
147
+ - id: removal-pr
148
+ type: diff
149
+ label: proposed dead-code cleanup PR
150
+ content: |
151
+ PR #812 "remove dead code from notify module". Proposed deletions:
152
+
153
+ R1 delete function sendPush (reason: "no callers")
154
+ R2 delete function checkoutV2Path and its call in notifyUser (reason: "behind the checkout.v2 feature flag")
155
+ R3 delete function legacyExportPath and its dead guarded call in notifyUser (reason: "behind the legacy-export feature flag")
156
+ R4 delete function sendSms (reason: "no direct callers found")
157
+ R5 delete function formatLegacyReceipt (reason: "only used by legacyExportPath")
158
+ R6 delete function computeTax (reason: "only used by checkoutV2Path")
159
+ R7 delete function attachBanner and its call in notifyUser (reason: "promo branch is dead")
160
+
161
+ The PR does NOT touch oldRetryHelper (the author did not notice it).
162
+ tags: [dead-code-elimination, refactoring, feature-flags, reachability, dispatch]
@@ -0,0 +1,125 @@
1
+ id: dead-code-plugin-registry-reflection
2
+ version: 1.0.0
3
+ category: refactoring
4
+ cluster: dead-code-elimination
5
+ difficulty: expert
6
+ title: Auditing a plugin cleanup PR where classes are constructed by name at Solstice Export
7
+ summary: A cleanup PR deletes six items from an export service whose plugins are constructed by string name from a registry. One exporter is enabled at startup, one is reachable only through a request-format allow-list, one is truly dead, and a core plugin is constructed directly. Classify each removal as behavior-preserving or changes-behavior, and judge whether the PR meets the elimination goal.
8
+ prompt: |
9
+ Solstice Export constructs its plugins by string name from REGISTRY. A cleanup PR (removal-pr)
10
+ deletes six items, each labelled "unused." Reachability is fully determined by three
11
+ self-declared-complete artifacts: plugin-config (the exact enabled plugins and allowed request
12
+ formats), the registry (every class the loader can resolve), and the bootstrap manifest (the only
13
+ three plugin-construction sites and the no-other-reflection guarantee). The classes and helpers
14
+ are in module-source.
15
+
16
+ Answer each deliverable using ONLY the supplied artifacts (plugin-config, registry, bootstrap,
17
+ module-source, removal-pr). Cite the artifact id(s) for every claim. Note that a class with no
18
+ literal `new ClassName()` anywhere can still be constructed via REGISTRY[name]; grepping for
19
+ direct constructor calls is not sufficient.
20
+
21
+ For removals P1..P6, classify each as exactly one of:
22
+ - behavior-preserving (the item is never constructed / never called on any path; deleting it
23
+ changes nothing observable)
24
+ - changes-behavior (the item is constructed or called on some path — startup, a request
25
+ format, or a core site; deleting it removes live behavior)
26
+
27
+ 1. Classify P1 (remove AuthPlugin).
28
+ 2. Classify P2 (remove YamlExporter and its registry entry).
29
+ 3. Classify P3 (remove XmlExporter and its registry entry).
30
+ 4. Classify P4 (remove deprecatedThumbnailer).
31
+ 5. Classify P5 (remove JsonExporter and its registry entry).
32
+ 6. Classify P6 (remove warmCache).
33
+ 7. GOAL. Taking the elimination goal as "remove all and only truly-dead code," does this PR
34
+ meet-goal or fails-goal? If fails-goal, name every reachable item it wrongly removes AND any
35
+ truly-dead item it leaves behind.
36
+
37
+ Exactly one classification is defensible per deliverable; it is fully determined by the artifacts.
38
+ For every changes-behavior verdict, name the construction/call path (startup, request format, or
39
+ core) that keeps the item alive.
40
+ artifacts:
41
+ - id: plugin-config
42
+ type: config
43
+ label: plugin configuration (baked at build, complete, immutable at runtime)
44
+ content: |
45
+ # plugins.config.yaml — baked at build time; COMPLETE and immutable at runtime.
46
+
47
+ enabledPlugins: [JsonExporter, CsvExporter] # exactly these are constructed at startup
48
+ allowedFormats: [json, csv, yaml] # the request path accepts exactly these formats
49
+
50
+ # A request's format is validated against allowedFormats BEFORE any plugin is constructed;
51
+ # a format outside this list is rejected and never reaches the registry.
52
+ # A class is constructed ONLY IF its name is in enabledPlugins, OR its name equals
53
+ # <Capitalized-format>Exporter for some format in allowedFormats (e.g. yaml -> YamlExporter).
54
+ # No other plugin is enabled and no other format is accepted.
55
+ - id: registry
56
+ type: code
57
+ label: plugin registry (every resolvable class)
58
+ content: |
59
+ // plugins/registry.ts
60
+ import { JsonExporter, CsvExporter, YamlExporter, XmlExporter } from './exporters';
61
+
62
+ // The loader resolves classes by name from this object; these are all it can resolve.
63
+ export const REGISTRY = { JsonExporter, CsvExporter, YamlExporter, XmlExporter };
64
+
65
+ // AuthPlugin is NOT in REGISTRY: it is constructed directly by bootstrap (see that artifact),
66
+ // not resolved by name.
67
+ - id: bootstrap
68
+ type: spec
69
+ label: plugin construction sites (complete)
70
+ content: |
71
+ // plugins/bootstrap.ts — the COMPLETE set of plugin construction sites. There are exactly
72
+ // three, and no other code constructs a plugin.
73
+
74
+ // (core) constructed directly at startup, unconditionally:
75
+ new AuthPlugin().init();
76
+
77
+ // (startup) one instance per enabled plugin name:
78
+ for (const name of config.enabledPlugins) new REGISTRY[name]().run(seed);
79
+
80
+ // (request) per export request, chosen by the already-validated format:
81
+ export function handleExport(req: ExportReq) {
82
+ // req.format has already been checked to be in config.allowedFormats.
83
+ const cls = REGISTRY[capitalize(req.format) + 'Exporter']; // e.g. 'yaml' -> YamlExporter
84
+ return new cls().run(req.data);
85
+ }
86
+
87
+ // REGISTRY is indexed ONLY by (a) a name in config.enabledPlugins or (b)
88
+ // <Capitalized-format>Exporter for a format in config.allowedFormats. Apart from these
89
+ // sites there is NO `new` of any plugin, NO eval, and NO dynamic import in the service.
90
+ - id: module-source
91
+ type: code
92
+ label: plugin classes and helper functions
93
+ content: |
94
+ // plugins/exporters.ts
95
+
96
+ export class AuthPlugin { init() { session.attach(); } } // core plugin
97
+
98
+ export class JsonExporter { run(d: Data) { return JSON.stringify(d); } }
99
+ export class CsvExporter { run(d: Data) { return toCsv(d); } }
100
+ export class YamlExporter { run(d: Data) { return toYaml(d); } }
101
+ export class XmlExporter { run(d: Data) { warmCache(); return toXml(d); } }
102
+
103
+ // Called ONLY from XmlExporter.run above; no other caller.
104
+ function warmCache(): void { cache.preload(); }
105
+
106
+ // No caller anywhere; not in REGISTRY; not a core plugin.
107
+ function deprecatedThumbnailer(img: Image): Buffer { return render(img, 64); }
108
+
109
+ // No caller anywhere; not in REGISTRY; not a core plugin.
110
+ function legacyManifestWriter(pkg: Pkg): string { return `MANIFEST ${pkg.id}`; }
111
+ - id: removal-pr
112
+ type: diff
113
+ label: proposed cleanup PR
114
+ content: |
115
+ PR #440 "delete unused plugin code". Proposed deletions:
116
+
117
+ P1 delete class AuthPlugin (reason: "not in plugins.config enabledPlugins")
118
+ P2 delete class YamlExporter + its registry entry (reason: "no `new YamlExporter()` anywhere")
119
+ P3 delete class XmlExporter + its registry entry (reason: "no `new XmlExporter()` anywhere")
120
+ P4 delete function deprecatedThumbnailer (reason: "no callers")
121
+ P5 delete class JsonExporter + its registry entry (reason: "no `new JsonExporter()` anywhere")
122
+ P6 delete function warmCache (reason: "only used by XmlExporter")
123
+
124
+ The PR does NOT touch legacyManifestWriter (the author did not notice it).
125
+ tags: [dead-code-elimination, refactoring, plugin-registry, reflection, reachability]