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,237 @@
1
+ id: dependency-decoupling-inject-clock
2
+ version: 1.0.0
3
+ category: refactoring
4
+ cluster: dependency-decoupling
5
+ difficulty: expert
6
+ title: Decoupling Ledger From the Global Clock — Import Cycles, Singleton Sharing, and Init Order
7
+ summary: >-
8
+ A Ledger is tightly coupled to a global Date.now(), the shared db singleton, and a config the
9
+ composition root overrides at startup. Four rewrites inject a Clock. Exactly one decouples the clock
10
+ while preserving the shared db instance, construction-after-override, and an acyclic import graph.
11
+ The decoys close an import cycle, construct a second database, or build the ledger before the
12
+ override. Classify each and name the observable difference.
13
+ prompt: |
14
+ Answer each deliverable using ONLY the supplied artifacts (config-mod, db-mod, ledger-original,
15
+ module-graph, rewrite-a, rewrite-b, rewrite-c, rewrite-d). Cite artifact id(s) for every claim. Use
16
+ the verdict vocabulary: behavior-preserving / changes-behavior / meets-goal / fails-goal.
17
+
18
+ The transformation goal is to decouple Ledger.record from the global Date.now() by injecting a Clock,
19
+ while preserving all four properties module-graph lists: (1) the single shared `db` instance used
20
+ app-wide, (2) construction of the ledger AFTER main sets config.currency = 'EUR' (so entries stamp
21
+ 'EUR'), (3) no import cycle among these modules, and (4) in production a timestamp equal to
22
+ Date.now().
23
+
24
+ 1. Classify rewrite-a. Justify against all four preservation properties and the injection goal.
25
+
26
+ 2. Classify rewrite-b. If it changes behavior, name the exact import cycle it introduces (the edges)
27
+ and state what goes wrong at startup and why, per module-graph's load order.
28
+
29
+ 3. Classify rewrite-c. Name the observable difference from ledger-original in terms of which
30
+ Database instance ledger.record writes to, and the consequence for entries written through the
31
+ ledger.
32
+
33
+ 4. Classify rewrite-d. State what currency a ledger entry carries and why, tracing the load order
34
+ from module-graph.
35
+
36
+ 5. Exactly one rewrite decouples the clock without an import cycle, keeps the shared db singleton,
37
+ and constructs the ledger after the override. Name it. Then, for ledger-original, state the
38
+ ground truth: what currency does a ledger entry carry, and does the ledger write to the shared db
39
+ singleton?
40
+ artifacts:
41
+ - id: config-mod
42
+ type: code
43
+ label: app/config.ts — the mutable config singleton
44
+ content: |
45
+ // app/config.ts — a single mutable config object, created at import.
46
+ export const config = {
47
+ currency: 'USD', // default; the composition root may override this before wiring services
48
+ dbUrl: 'postgres://prod/ledger',
49
+ };
50
+ - id: db-mod
51
+ type: code
52
+ label: app/db.ts — the shared database singleton
53
+ content: |
54
+ // app/db.ts — the shared database singleton, connected at import time from config.
55
+ import { config } from './config';
56
+
57
+ export class Database {
58
+ constructor(public readonly url: string) {}
59
+ write(entry: unknown): void {
60
+ /* append the entry to this connection */
61
+ }
62
+ }
63
+
64
+ export const db = new Database(config.dbUrl); // ONE connection, shared app-wide
65
+ - id: ledger-original
66
+ type: code
67
+ label: app/ledger.ts + app/main.ts — original (the reference behavior)
68
+ content: |
69
+ // app/ledger.ts — ORIGINAL: coupled to the db singleton, config, and the global clock.
70
+ import { db, Database } from './db';
71
+ import { config } from './config';
72
+
73
+ export class Ledger {
74
+ constructor(
75
+ private readonly database: Database,
76
+ private readonly currency: string,
77
+ ) {}
78
+ record(amount: number): void {
79
+ this.database.write({ amount, currency: this.currency, at: Date.now() }); // global clock
80
+ }
81
+ }
82
+
83
+ // app/main.ts — the composition root (the ONLY one).
84
+ import { db } from './db';
85
+ import { config } from './config';
86
+ import { Ledger } from './ledger';
87
+
88
+ config.currency = 'EUR'; // override BEFORE wiring services
89
+ export const ledger = new Ledger(db, config.currency); // built after the override -> 'EUR'
90
+ - id: module-graph
91
+ type: note
92
+ label: app module graph — complete modules, import edges, load order, startup
93
+ content: |
94
+ app module graph — the complete set of modules, import edges, and load order.
95
+
96
+ Modules (the COMPLETE set): config.ts, db.ts, clock.ts (added by the decoupling), ledger.ts,
97
+ main.ts. main.ts is the ONLY composition root / entry point.
98
+
99
+ Original import edges (a DAG, no cycle):
100
+ db.ts -> config.ts
101
+ ledger.ts -> db.ts, config.ts
102
+ main.ts -> db.ts, config.ts, ledger.ts
103
+
104
+ ES module load order for the original (depth-first from the entry main.ts): config.ts, then
105
+ db.ts (which constructs the shared `db` singleton), then ledger.ts, then the main.ts body. A
106
+ module's top-level code runs once, when it is first imported.
107
+
108
+ Startup sequence in main.ts (authoritative): main sets config.currency = 'EUR' and THEN
109
+ constructs the ledger. So in the original the ledger is constructed AFTER the override and
110
+ stamps currency 'EUR'. The `db` singleton in db.ts is the ONE database connection the whole app
111
+ shares.
112
+
113
+ An acceptable decoupling must keep all of: (1) the single shared `db` instance used app-wide,
114
+ (2) construction of the ledger AFTER the config.currency override (so entries stamp 'EUR'),
115
+ (3) no import cycle among these modules, and (4) in production a timestamp equal to Date.now().
116
+ A circular import here means a module reads another module's export before that export has been
117
+ initialized.
118
+ - id: rewrite-a
119
+ type: code
120
+ label: rewrite-a — Clock leaf module, injected in main
121
+ content: |
122
+ // app/clock.ts — a LEAF module (imports nothing).
123
+ export interface Clock {
124
+ now(): number;
125
+ }
126
+ export const systemClock: Clock = { now: () => Date.now() };
127
+
128
+ // app/ledger.ts (rewrite-a) — receives the db and clock; imports types only.
129
+ import type { Database } from './db';
130
+ import type { Clock } from './clock';
131
+ export class Ledger {
132
+ constructor(
133
+ private readonly database: Database,
134
+ private readonly currency: string,
135
+ private readonly clock: Clock,
136
+ ) {}
137
+ record(amount: number): void {
138
+ this.database.write({ amount, currency: this.currency, at: this.clock.now() });
139
+ }
140
+ }
141
+
142
+ // app/main.ts (rewrite-a)
143
+ import { db } from './db';
144
+ import { config } from './config';
145
+ import { Ledger } from './ledger';
146
+ import { systemClock } from './clock';
147
+ config.currency = 'EUR';
148
+ export const ledger = new Ledger(db, config.currency, systemClock);
149
+ - id: rewrite-b
150
+ type: code
151
+ label: rewrite-b — clock imported from main
152
+ content: |
153
+ // app/clock.ts — same leaf module as rewrite-a.
154
+ export interface Clock { now(): number; }
155
+ export const systemClock: Clock = { now: () => Date.now() };
156
+
157
+ // app/ledger.ts (rewrite-b) — pulls the clock FROM main and builds the ledger at import.
158
+ import { db, Database } from './db';
159
+ import { config } from './config';
160
+ import { clock } from './main'; // NEW edge: ledger.ts -> main.ts
161
+ export class Ledger {
162
+ constructor(
163
+ private readonly database: Database,
164
+ private readonly currency: string,
165
+ private readonly clock: Clock,
166
+ ) {}
167
+ record(amount: number): void {
168
+ this.database.write({ amount, currency: this.currency, at: this.clock.now() });
169
+ }
170
+ }
171
+ export const ledger = new Ledger(db, config.currency, clock); // runs at import of ledger.ts
172
+
173
+ // app/main.ts (rewrite-b)
174
+ import { Ledger } from './ledger'; // main.ts -> ledger.ts (closes the cycle)
175
+ import { systemClock } from './clock';
176
+ export const clock = systemClock;
177
+ - id: rewrite-c
178
+ type: code
179
+ label: rewrite-c — ledger builds its own Database
180
+ content: |
181
+ // app/clock.ts — same leaf module as rewrite-a.
182
+ export interface Clock { now(): number; }
183
+ export const systemClock: Clock = { now: () => Date.now() };
184
+
185
+ // app/ledger.ts (rewrite-c) — clock injected, but the ledger creates its OWN database.
186
+ import { Database } from './db';
187
+ import { config } from './config';
188
+ import type { Clock } from './clock';
189
+ export class Ledger {
190
+ private readonly database = new Database(config.dbUrl); // NEW connection, not the shared db
191
+ constructor(
192
+ private readonly currency: string,
193
+ private readonly clock: Clock,
194
+ ) {}
195
+ record(amount: number): void {
196
+ this.database.write({ amount, currency: this.currency, at: this.clock.now() });
197
+ }
198
+ }
199
+
200
+ // app/main.ts (rewrite-c)
201
+ import { config } from './config';
202
+ import { Ledger } from './ledger';
203
+ import { systemClock } from './clock';
204
+ config.currency = 'EUR';
205
+ export const ledger = new Ledger(config.currency, systemClock);
206
+ - id: rewrite-d
207
+ type: code
208
+ label: rewrite-d — ledger constructed at import time
209
+ content: |
210
+ // app/clock.ts — same leaf module as rewrite-a.
211
+ export interface Clock { now(): number; }
212
+ export const systemClock: Clock = { now: () => Date.now() };
213
+
214
+ // app/ledger.ts (rewrite-d) — clock injected and db shared, but the ledger is built at import.
215
+ import { db } from './db';
216
+ import { config } from './config';
217
+ import { systemClock } from './clock';
218
+ import type { Database } from './db';
219
+ import type { Clock } from './clock';
220
+ export class Ledger {
221
+ constructor(
222
+ private readonly database: Database,
223
+ private readonly currency: string,
224
+ private readonly clock: Clock,
225
+ ) {}
226
+ record(amount: number): void {
227
+ this.database.write({ amount, currency: this.currency, at: this.clock.now() });
228
+ }
229
+ }
230
+ export const ledger = new Ledger(db, config.currency, systemClock); // runs at import of ledger.ts
231
+
232
+ // app/main.ts (rewrite-d)
233
+ import { config } from './config';
234
+ import './ledger'; // importing ledger triggers its construction
235
+ config.currency = 'EUR'; // override runs AFTER ledger is already built
236
+ export { ledger } from './ledger';
237
+ tags: [refactoring, dependency-decoupling, dependency-injection, import-cycle, singleton]
@@ -0,0 +1,177 @@
1
+ id: dependency-decoupling-invert-middleware
2
+ version: 1.0.0
3
+ category: refactoring
4
+ cluster: dependency-decoupling
5
+ difficulty: expert
6
+ title: Inverting the Middleware Chain — Registration Order, a Shared Registry, and Import Cycles
7
+ summary: >-
8
+ A hard-coded middleware chain (auth, rateLimit, log) is to be inverted so each middleware
9
+ self-registers via an imported register(). Exactly one inversion preserves the execution order and
10
+ the single shared registry with no import cycle. The decoys import the middleware in the wrong
11
+ order, hand out a fresh registry per call, or make a middleware import app and close a cycle.
12
+ Classify each and name the observable difference on the final context.
13
+ prompt: |
14
+ Answer each deliverable using ONLY the supplied artifacts (pipeline-original, registry-note,
15
+ rewrite-a, rewrite-b, rewrite-c, rewrite-d). Cite artifact id(s) for every claim. Use the verdict
16
+ vocabulary: behavior-preserving / changes-behavior / meets-goal / fails-goal.
17
+
18
+ The transformation goal is to invert control so each middleware self-registers via register(), while
19
+ preserving: the CANONICAL execution order auth -> rateLimit -> log (registry-note), the single shared
20
+ registry so all three run against the same ctx passed to run(ctx), and an acyclic import graph. The
21
+ observable outcome of handle(ctx) is the final ctx (ctx.user, ctx.rateBucket, ctx.log).
22
+
23
+ 1. Classify rewrite-a. Justify against execution order, shared registry, and the acyclic-import
24
+ requirement.
25
+
26
+ 2. Classify rewrite-b. If it changes behavior, give the resulting execution order and the concrete
27
+ final value of ctx.log after handle(ctx), and compare to pipeline-original.
28
+
29
+ 3. Classify rewrite-c. State which middlewares actually run against ctx and what the final ctx
30
+ (ctx.user, ctx.rateBucket, ctx.log) looks like, and name the mechanism.
31
+
32
+ 4. Classify rewrite-d. Name the exact import cycle (the edges) and state what happens at startup and
33
+ why, per registry-note's load-order rule.
34
+
35
+ 5. Exactly one inversion preserves order, keeps one shared registry, and stays acyclic. Name it.
36
+ Then, for pipeline-original, state the ground truth: the execution order and the final ctx.log and
37
+ ctx.rateBucket after handle(ctx).
38
+ artifacts:
39
+ - id: pipeline-original
40
+ type: code
41
+ label: app/context.ts + app/app.ts — original inline chain (the reference behavior)
42
+ content: |
43
+ // app/context.ts — the shared request Context.
44
+ export interface Context {
45
+ user?: string;
46
+ rateBucket?: string;
47
+ log: string[];
48
+ }
49
+
50
+ // app/app.ts — ORIGINAL: the middleware chain is built inline, in a fixed order.
51
+ import { Context } from './context';
52
+
53
+ function auth(ctx: Context): void { ctx.user = 'u-42'; }
54
+ function rateLimit(ctx: Context): void { ctx.rateBucket = ctx.user ?? 'anon'; } // reads ctx.user
55
+ function log(ctx: Context): void { ctx.log.push(`user=${ctx.user ?? 'none'}`); } // reads ctx.user
56
+
57
+ export function handle(ctx: Context): void {
58
+ auth(ctx); // 1
59
+ rateLimit(ctx); // 2
60
+ log(ctx); // 3
61
+ // ...then the route handler runs
62
+ }
63
+ - id: registry-note
64
+ type: note
65
+ label: pipeline module graph — canonical order, registry sharing, load order (complete)
66
+ content: |
67
+ pipeline module graph — canonical order, registry sharing, and load order (complete).
68
+
69
+ The request pipeline runs three middlewares in this CANONICAL execution order:
70
+ 1. auth -> sets ctx.user = 'u-42'
71
+ 2. rateLimit -> sets ctx.rateBucket = ctx.user ?? 'anon' (reads ctx.user)
72
+ 3. log -> pushes `user=${ctx.user ?? 'none'}` to ctx.log (reads ctx.user)
73
+ Then the route handler runs. These are the COMPLETE set of middlewares, and app is the ONLY
74
+ place they are wired. The observable outcome of handle(ctx) is the final ctx (ctx.user,
75
+ ctx.rateBucket, ctx.log).
76
+
77
+ The inversion introduces registry.ts. In an acceptable inversion:
78
+ - registry.ts is a LEAF module (imports nothing) exposing a SINGLE shared list; register(fn)
79
+ appends to it and run(ctx) invokes the registered fns in registration (insertion) order.
80
+ - Each middleware module calls register(...) at import, and app.ts imports the middleware
81
+ modules so that registration order equals the CANONICAL execution order above, then calls
82
+ run(ctx).
83
+ - No import cycle: middleware -> registry, app -> middleware + registry, registry -> nothing.
84
+
85
+ A circular import here means a middleware imports app (or app is otherwise re-entered) so that a
86
+ value is read before it is initialized. run(ctx) invokes handlers in the order they were
87
+ registered, which is the order app.ts imports the middleware modules.
88
+ - id: rewrite-a
89
+ type: code
90
+ label: rewrite-a — shared registry, imports in canonical order
91
+ content: |
92
+ // app/registry.ts — a LEAF module (imports nothing); ONE shared chain.
93
+ import type { Context } from './context';
94
+ type Middleware = (ctx: Context) => void;
95
+ const chain: Middleware[] = [];
96
+ export function register(fn: Middleware): void { chain.push(fn); }
97
+ export function run(ctx: Context): void { for (const fn of chain) fn(ctx); } // insertion order
98
+
99
+ // app/auth.ts
100
+ import { register } from './registry';
101
+ register((ctx) => { ctx.user = 'u-42'; });
102
+ // app/rateLimit.ts
103
+ import { register } from './registry';
104
+ register((ctx) => { ctx.rateBucket = ctx.user ?? 'anon'; });
105
+ // app/log.ts
106
+ import { register } from './registry';
107
+ register((ctx) => { ctx.log.push(`user=${ctx.user ?? 'none'}`); });
108
+
109
+ // app/app.ts — import order equals the canonical execution order.
110
+ import './auth';
111
+ import './rateLimit';
112
+ import './log';
113
+ import { run } from './registry';
114
+ export function handle(ctx) { run(ctx); }
115
+ - id: rewrite-b
116
+ type: code
117
+ label: rewrite-b — shared registry, imports reordered
118
+ content: |
119
+ // app/registry.ts, app/auth.ts, app/rateLimit.ts, app/log.ts — identical to rewrite-a
120
+ // (single shared chain; each middleware self-registers at import).
121
+
122
+ // app/app.ts — import order differs from the canonical execution order.
123
+ import './log';
124
+ import './auth';
125
+ import './rateLimit';
126
+ import { run } from './registry';
127
+ export function handle(ctx) { run(ctx); }
128
+ - id: rewrite-c
129
+ type: code
130
+ label: rewrite-c — a fresh registry per call
131
+ content: |
132
+ // app/registry.ts — hands out a NEW registry instance on every call.
133
+ import type { Context } from './context';
134
+ type Middleware = (ctx: Context) => void;
135
+ export class Registry {
136
+ private fns: Middleware[] = [];
137
+ register(fn: Middleware): void { this.fns.push(fn); }
138
+ run(ctx: Context): void { for (const fn of this.fns) fn(ctx); }
139
+ }
140
+ export function getRegistry(): Registry { return new Registry(); } // new instance each call
141
+
142
+ // app/auth.ts
143
+ import { getRegistry } from './registry';
144
+ getRegistry().register((ctx) => { ctx.user = 'u-42'; });
145
+ // app/rateLimit.ts
146
+ import { getRegistry } from './registry';
147
+ getRegistry().register((ctx) => { ctx.rateBucket = ctx.user ?? 'anon'; });
148
+ // app/log.ts
149
+ import { getRegistry } from './registry';
150
+ getRegistry().register((ctx) => { ctx.log.push(`user=${ctx.user ?? 'none'}`); });
151
+
152
+ // app/app.ts — canonical import order.
153
+ import './auth';
154
+ import './rateLimit';
155
+ import './log';
156
+ import { getRegistry } from './registry';
157
+ export function handle(ctx) { getRegistry().run(ctx); }
158
+ - id: rewrite-d
159
+ type: code
160
+ label: rewrite-d — auth imports a constant from app
161
+ content: |
162
+ // app/registry.ts, app/rateLimit.ts, app/log.ts — identical to rewrite-a (shared chain).
163
+
164
+ // app/auth.ts — reads a constant from app at import.
165
+ import { register } from './registry';
166
+ import { DEFAULT_USER } from './app'; // NEW edge: auth.ts -> app.ts
167
+ const u = DEFAULT_USER; // read at import (top-level)
168
+ register((ctx) => { ctx.user = u; });
169
+
170
+ // app/app.ts — imports the middleware (app.ts -> auth.ts), then declares the constant.
171
+ import './auth';
172
+ import './rateLimit';
173
+ import './log';
174
+ import { run } from './registry';
175
+ export const DEFAULT_USER = 'u-42';
176
+ export function handle(ctx) { run(ctx); }
177
+ tags: [refactoring, dependency-decoupling, inversion-of-control, import-cycle, ordering]
@@ -0,0 +1,132 @@
1
+ id: extract-and-inline-closure-capture
2
+ version: 1.0.0
3
+ category: refactoring
4
+ cluster: extract-and-inline
5
+ difficulty: expert
6
+ title: Extracting makeHandler From a Loop — Per-Iteration Capture, Shared var, and Eager Render
7
+ summary: >-
8
+ A loop that registers deferred event handlers is to be refactored by extracting the handler-building
9
+ logic into a makeHandler helper. Exactly one extraction preserves, for every registered handler,
10
+ which widget object render runs on, the index it receives, and that render runs only on emit. The
11
+ decoys share a var across iterations, capture only the index wrong, or call render eagerly at
12
+ registration. Classify each and name the observable difference.
13
+ prompt: |
14
+ Answer each deliverable using ONLY the supplied artifacts (register-original, bus-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 lift the handler-building logic out of the loop into a reusable helper
19
+ WITHOUT changing, for each registered event, (i) which widget object render is called on, (ii) the
20
+ index argument render receives, or (iii) that render runs only when the event is emitted, never
21
+ during registration (see bus-note for what is observable).
22
+
23
+ 1. Classify rewrite-a relative to register-original. Justify by tracing what each registered handler
24
+ captures across iterations.
25
+
26
+ 2. Classify rewrite-b. If it changes behavior, state precisely: when the event 'refresh:' +
27
+ widgets[0].id is later emitted, which widget object render is called on and what index argument
28
+ it receives.
29
+
30
+ 3. Classify rewrite-c. Name the observable difference from register-original, in terms of when
31
+ render runs and how many times, for widgets that are never emitted.
32
+
33
+ 4. Classify rewrite-d. If it changes behavior, state what render receives (widget object and index)
34
+ when 'refresh:' + widgets[0].id is emitted, and how this differs from rewrite-b.
35
+
36
+ 5. Exactly one extraction is the only behavior-preserving refactor. Name it. Then, for
37
+ register-original, state the ground truth: when 'refresh:' + widgets[0].id is emitted, which
38
+ widget object render is called on, with what index, and at what time.
39
+ artifacts:
40
+ - id: register-original
41
+ type: code
42
+ label: dashboard/wire.ts — original inline loop (the reference behavior)
43
+ content: |
44
+ // dashboard/wire.ts — original. registerWidgets is the ONLY function that wires refresh
45
+ // handlers (see bus-note).
46
+ import type { EventBus } from './bus';
47
+ import type { Widget } from './widget';
48
+
49
+ export function registerWidgets(bus: EventBus, widgets: Widget[]): void {
50
+ for (let i = 0; i < widgets.length; i++) {
51
+ const widget = widgets[i]; // per-iteration block binding
52
+ bus.on(`refresh:${widget.id}`, () => {
53
+ widget.render(i); // captures THIS iteration's widget and i
54
+ });
55
+ }
56
+ }
57
+ - id: bus-note
58
+ type: note
59
+ label: dashboard wiring — EventBus semantics, render observability, completeness
60
+ content: |
61
+ dashboard wiring — EventBus semantics and observability (complete).
62
+
63
+ EventBus.on(event, handler) registers handler under the event name; it does NOT call handler.
64
+ Later, EventBus.emit(event) invokes every handler registered under that event, in registration
65
+ order. Handlers may be invoked zero, one, or many times depending on runtime events.
66
+
67
+ widget.render(index) paints the widget and increments a global paint_count (an observable side
68
+ effect); the index argument is recorded in the paint. render is a method on the widget object
69
+ (it reads its own `this`), so which widget object a handler calls render on is observable, as is
70
+ the index argument.
71
+
72
+ registerWidgets is the ONLY function that wires refresh handlers, and it is called exactly once
73
+ at startup. The `widgets` array is frozen after registration: neither its length nor its element
74
+ identities change. This is the complete wiring surface.
75
+
76
+ Reference behavior of register-original: for each index k, a handler is registered under
77
+ 'refresh:' + widgets[k].id that, WHEN LATER EMITTED, calls widgets[k].render(k) — the widget
78
+ object and the index captured at iteration k. render is not called during registration.
79
+ - id: rewrite-a
80
+ type: code
81
+ label: rewrite-a — makeHandler(widget, i) by value
82
+ content: |
83
+ // rewrite-a — extract a helper that takes the widget and index by value.
84
+ function makeHandler(widget: Widget, i: number): () => void {
85
+ return () => widget.render(i);
86
+ }
87
+
88
+ export function registerWidgets(bus: EventBus, widgets: Widget[]): void {
89
+ for (let i = 0; i < widgets.length; i++) {
90
+ const widget = widgets[i];
91
+ bus.on(`refresh:${widget.id}`, makeHandler(widget, i));
92
+ }
93
+ }
94
+ - id: rewrite-b
95
+ type: code
96
+ label: rewrite-b — hoisted var bindings
97
+ content: |
98
+ // rewrite-b — "hoist the temporaries out of the loop body."
99
+ export function registerWidgets(bus: EventBus, widgets: Widget[]): void {
100
+ for (var i = 0; i < widgets.length; i++) {
101
+ var widget = widgets[i]; // var: one shared binding for the whole loop
102
+ bus.on(`refresh:${widget.id}`, () => widget.render(i));
103
+ }
104
+ }
105
+ - id: rewrite-c
106
+ type: code
107
+ label: rewrite-c — makeHandler computes render up front
108
+ content: |
109
+ // rewrite-c — "precompute the render inside the helper."
110
+ function makeHandler(widget: Widget, i: number): () => void {
111
+ const result = widget.render(i); // render invoked here, during registration
112
+ return () => result;
113
+ }
114
+
115
+ export function registerWidgets(bus: EventBus, widgets: Widget[]): void {
116
+ for (let i = 0; i < widgets.length; i++) {
117
+ const widget = widgets[i];
118
+ bus.on(`refresh:${widget.id}`, makeHandler(widget, i));
119
+ }
120
+ }
121
+ - id: rewrite-d
122
+ type: code
123
+ label: rewrite-d — const widget, var i
124
+ content: |
125
+ // rewrite-d — "keep widget local, hoist just the counter."
126
+ export function registerWidgets(bus: EventBus, widgets: Widget[]): void {
127
+ for (var i = 0; i < widgets.length; i++) { // var i: shared counter
128
+ const widget = widgets[i]; // const: per-iteration binding
129
+ bus.on(`refresh:${widget.id}`, () => widget.render(i));
130
+ }
131
+ }
132
+ tags: [refactoring, extract-and-inline, closure, capture, evaluation-order]
@@ -0,0 +1,120 @@
1
+ id: extract-and-inline-short-circuit-side-effect
2
+ version: 1.0.0
3
+ category: refactoring
4
+ cluster: extract-and-inline
5
+ difficulty: expert
6
+ title: Inlining hasFunds — Short-Circuit Side Effects, the >= Boundary, and Meets-Goal vs Fails-Goal
7
+ summary: >-
8
+ A validator delegates to hasFunds, whose only side effect (a funds_check metric) fires only when a
9
+ && short-circuit reaches it. The task is to inline hasFunds and remove it. One rewrite preserves
10
+ behavior and meets the goal; one increments the metric unconditionally; one flips >= to >; one
11
+ preserves behavior but never actually inlines. Classify each against both behavior and goal.
12
+ prompt: |
13
+ Answer each deliverable using ONLY the supplied artifacts (validate-original, order-note, rewrite-a,
14
+ rewrite-b, rewrite-c, rewrite-d). Cite artifact id(s) for every claim. Use the verdict vocabulary:
15
+ behavior-preserving / changes-behavior / meets-goal / fails-goal.
16
+
17
+ The transformation goal is to INLINE hasFunds into canCheckout and REMOVE the hasFunds function,
18
+ while preserving observable behavior exactly: the boolean canCheckout returns for every order, AND
19
+ the number of times metrics.inc('funds_check') fires (see order-note — the && short-circuit means it
20
+ fires only when order.itemsValid is true).
21
+
22
+ 1. Classify rewrite-a against BOTH dimensions (behavior vs validate-original, and whether it meets
23
+ the inline goal). Justify by tracing the itemsValid=false and itemsValid=true paths.
24
+
25
+ 2. Classify rewrite-b. If it changes behavior, name the observable difference and give the exact
26
+ order shape that reveals it, stating how many funds_check increments validate-original vs
27
+ rewrite-b produce on that order.
28
+
29
+ 3. Classify rewrite-c. Give the exact order field values (itemsValid, balance, total) on which its
30
+ return value differs from validate-original, and state both return values.
31
+
32
+ 4. Classify rewrite-d against BOTH dimensions. If its behavior matches validate-original, say so, and
33
+ separately state whether it satisfies the transformation goal, with the reason.
34
+
35
+ 5. Exactly one rewrite both preserves behavior AND meets the inline goal. Name it. Then, for
36
+ validate-original, state the ground truth: when order.itemsValid is false, how many times does
37
+ metrics.inc('funds_check') fire, and what does canCheckout return?
38
+ artifacts:
39
+ - id: validate-original
40
+ type: code
41
+ label: checkout/validate.ts — shipped validator (the reference behavior)
42
+ content: |
43
+ // checkout/validate.ts — the shipped validator.
44
+ import { metrics } from './metrics';
45
+ import type { Order } from './order';
46
+
47
+ function hasFunds(order: Order): boolean {
48
+ metrics.inc('funds_check'); // the ONLY producer of the funds_check counter
49
+ return order.balance >= order.total;
50
+ }
51
+
52
+ export function canCheckout(order: Order): boolean {
53
+ return order.itemsValid && hasFunds(order); // && short-circuits: hasFunds skipped if !itemsValid
54
+ }
55
+ - id: order-note
56
+ type: note
57
+ label: checkout — observability, the complete call surface, and the goal
58
+ content: |
59
+ checkout — observability and the complete call surface.
60
+
61
+ Order fields itemsValid (boolean), balance (number), total (number) are plain data properties:
62
+ reading them has no getter and no side effect. metrics.inc('funds_check') is the ONLY side
63
+ effect anywhere in this flow; the funds_check counter is watched on the ops dashboard.
64
+
65
+ hasFunds is the ONLY function that calls metrics.inc('funds_check'), and canCheckout is the ONLY
66
+ caller of hasFunds. So the number of funds_check increments per canCheckout call is fully
67
+ determined by canCheckout's control flow. This is the complete surface.
68
+
69
+ Reference behavior of canCheckout (validate-original): because `&&` short-circuits, hasFunds —
70
+ and therefore metrics.inc('funds_check') — runs if and only if order.itemsValid is true. When
71
+ itemsValid is true, canCheckout returns (order.balance >= order.total); when itemsValid is
72
+ false, it returns false and funds_check is NOT incremented. The comparison is `>=`, so an order
73
+ whose balance equals its total counts as funded.
74
+
75
+ The transformation goal is to INLINE hasFunds into canCheckout and remove the hasFunds function,
76
+ preserving that behavior exactly.
77
+ - id: rewrite-a
78
+ type: code
79
+ label: rewrite-a — inline with an early return
80
+ content: |
81
+ // rewrite-a — inline hasFunds, guard with an early return.
82
+ export function canCheckout(order: Order): boolean {
83
+ if (!order.itemsValid) return false;
84
+ metrics.inc('funds_check');
85
+ return order.balance >= order.total;
86
+ }
87
+ - id: rewrite-b
88
+ type: code
89
+ label: rewrite-b — compute the funds check, then AND it in
90
+ content: |
91
+ // rewrite-b — "compute the funds check up front, then AND it with itemsValid."
92
+ export function canCheckout(order: Order): boolean {
93
+ metrics.inc('funds_check');
94
+ const funded = order.balance >= order.total;
95
+ return order.itemsValid && funded;
96
+ }
97
+ - id: rewrite-c
98
+ type: code
99
+ label: rewrite-c — inline with an early return, tightened comparison
100
+ content: |
101
+ // rewrite-c — inline hasFunds, guard with an early return.
102
+ export function canCheckout(order: Order): boolean {
103
+ if (!order.itemsValid) return false;
104
+ metrics.inc('funds_check');
105
+ return order.balance > order.total;
106
+ }
107
+ - id: rewrite-d
108
+ type: code
109
+ label: rewrite-d — rewrite the caller as a ternary
110
+ content: |
111
+ // rewrite-d — "tidy the expression with a ternary."
112
+ function hasFunds(order: Order): boolean {
113
+ metrics.inc('funds_check');
114
+ return order.balance >= order.total;
115
+ }
116
+
117
+ export function canCheckout(order: Order): boolean {
118
+ return order.itemsValid ? hasFunds(order) : false;
119
+ }
120
+ tags: [refactoring, extract-and-inline, inline, short-circuit, side-effect]