bridgebench 3.1.0-alpha.1 → 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 (112) hide show
  1. package/README.md +141 -173
  2. package/dist/{chunk-UXFKTSQS.js → chunk-AY45YLYL.js} +105 -4
  3. package/dist/{chunk-MBC4C3VA.js → chunk-CJGHBY54.js} +7 -6
  4. package/dist/{chunk-3CX4FIFZ.js → chunk-DVMGL3L7.js} +80 -5
  5. package/dist/{chunk-KGZEQ6R5.js → chunk-IUPFMGUL.js} +152 -15
  6. package/dist/{chunk-XPFZBHV2.cjs → chunk-KCXQ5SAU.cjs} +21 -20
  7. package/dist/{chunk-SMG3PORS.cjs → chunk-QMOPRKWD.cjs} +89 -14
  8. package/dist/{chunk-5OZGHLDH.cjs → chunk-VAS6KNJA.cjs} +216 -79
  9. package/dist/{chunk-5PCDG7VA.cjs → chunk-X3LPZGHS.cjs} +106 -5
  10. package/dist/cli.cjs +51 -48
  11. package/dist/cli.js +9 -6
  12. package/dist/client.cjs +5 -5
  13. package/dist/client.d.cts +3 -3
  14. package/dist/client.d.ts +3 -3
  15. package/dist/client.js +4 -4
  16. package/dist/contracts/index.cjs +6 -2
  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 -3
  20. package/dist/index.cjs +8 -4
  21. package/dist/index.d.cts +9 -8
  22. package/dist/index.d.ts +9 -8
  23. package/dist/index.js +8 -4
  24. package/dist/{logger-QJU7SBDz.d.ts → logger-BByta-7V.d.cts} +23 -23
  25. package/dist/{logger-CCR9Mg1c.d.cts → logger-BQf29BLe.d.ts} +23 -23
  26. package/dist/{reports-4CejmOHf.d.cts → reports-B8TCJtPr.d.ts} +57 -13
  27. package/dist/{reports-s2CTnGN8.d.ts → reports-DPpOoOux.d.cts} +57 -13
  28. package/dist/{tasks-CpaCJ6JE.d.cts → tasks-BmhWuMBD.d.cts} +24 -22
  29. package/dist/{tasks-CpaCJ6JE.d.ts → tasks-BmhWuMBD.d.ts} +24 -22
  30. package/dist/tasks.cjs +3 -3
  31. package/dist/tasks.d.cts +1 -1
  32. package/dist/tasks.d.ts +1 -1
  33. package/dist/tasks.js +2 -2
  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
@@ -0,0 +1,177 @@
1
+ id: spec-conformance-slug-normalizer
2
+ version: 1.0.0
3
+ category: generation
4
+ cluster: spec-conformance
5
+ difficulty: expert
6
+ title: normalizeSlug — Which Of Five Submissions Conforms To Every Clause
7
+ summary: A frozen specification defines normalizeSlug(input) as a strict seven-clause pipeline. Five candidate implementations are under review; exactly one matches the spec on every input while each of the other four violates one specific, citable clause. Classify all five and name the distinguishing input for each near-miss.
8
+ prompt: |
9
+ Answer each deliverable using ONLY the supplied artifacts (normalizer-spec, test-vectors,
10
+ candidates-a, candidates-b, reviewer-thread, submission-manifest). Cite the artifact id(s)
11
+ for every claim. Do not invent requirements the spec does not state, and do not overlook a
12
+ clause it does state. The verdict vocabulary is exactly: conforms / violates.
13
+
14
+ The spec (normalizer-spec) declares clauses C1..C7 and is the complete contract. The
15
+ submission-manifest declares that candidate-1..candidate-5 are the entire set under review
16
+ and that exactly one is to be accepted.
17
+
18
+ 1. Classify each of candidate-1, candidate-2, candidate-3, candidate-4, candidate-5 as
19
+ `conforms` or `violates` against normalizer-spec.
20
+
21
+ 2. For every candidate you mark `violates`, name the single clause (one of C1..C7) it breaks
22
+ and give one concrete input string on which its output differs from the spec-correct
23
+ output. State both the spec-correct output and the candidate's output for that input.
24
+
25
+ 3. Exactly one candidate `conforms`. Identify it, and state the output it produces for the
26
+ test-vectors input "café_düsseldorf".
27
+
28
+ 4. In reviewer-thread, reviewer @ravi argues candidate-2 conforms because "it lowercases,
29
+ collapses separator runs to a single hyphen, trims, truncates to 40 with a trailing-hyphen
30
+ removal, and falls back to 'n-a' — every clause is represented." State candidate-2's verdict
31
+ and cite the one clause that refutes @ravi, with a discriminating input and both outputs.
32
+
33
+ 5. reviewer-thread references BUG-88: one submission returns an empty string ("") instead of
34
+ "n-a" when the input is entirely punctuation (e.g. "@@@"). Name the single candidate BUG-88
35
+ belongs to and the clause it breaks. Confirm no other candidate has this defect.
36
+ artifacts:
37
+ - id: normalizer-spec
38
+ type: spec
39
+ label: normalizeSlug specification (frozen, complete)
40
+ content: |
41
+ normalizeSlug(input: string): string
42
+
43
+ A pure, deterministic transform applied as an ORDERED pipeline. The clauses below are the
44
+ COMPLETE contract for this function; there are no other requirements. An implementation
45
+ conforms only if it produces the specified output for EVERY input, not merely if each clause
46
+ is "present" in some form.
47
+
48
+ C1 LOWERCASE. Map every character in the ASCII range U+0041..U+005A (A..Z) to its lowercase
49
+ form. Leave every other character unchanged. (No locale-specific casing; ASCII only.)
50
+
51
+ C2 SEPARATOR SET. After C1, a "separator" is any character NOT in the set [a-z0-9] (i.e. not
52
+ a lowercase ASCII letter and not an ASCII digit 0-9). Underscore '_', whitespace, all
53
+ punctuation, and every non-ASCII code point (e.g. 'é', 'ü', '—') are separators.
54
+
55
+ C3 COLLAPSE. Replace each maximal run of one-or-more separators with exactly one U+002D
56
+ hyphen-minus '-'. Two adjacent separators must not yield two hyphens.
57
+
58
+ C4 TRIM. Remove all leading and trailing '-' characters from the collapsed string.
59
+
60
+ C5 TRUNCATE. If the string's length exceeds 40, keep only the first 40 characters, then
61
+ remove a single trailing '-' if the 40th character is a hyphen. (Maximum length is 40;
62
+ truncation must never leave a trailing hyphen.)
63
+
64
+ C6 FALLBACK. If the string is now empty (length 0), return the literal "n-a". Otherwise
65
+ return the string. This clause is unconditional and does not depend on whether an
66
+ empty result is "expected" for a given input.
67
+
68
+ C7 DIGITS. ASCII digits 0-9 are preserved verbatim and are never treated as separators.
69
+
70
+ This is the entire specification. No other clause exists.
71
+ - id: test-vectors
72
+ type: table
73
+ label: authoritative input/output vectors (spec-correct)
74
+ content: |
75
+ These are the authoritative expected outputs computed from normalizer-spec. A conforming
76
+ implementation must reproduce every row exactly.
77
+
78
+ # input expected output
79
+ -- ------------------------------------------------ ---------------------------------------
80
+ 1 "Hello, World!" "hello-world"
81
+ 2 "Node.js ROCKS!!!" (two spaces) "node-js-rocks"
82
+ 3 "café_düsseldorf" "caf-d-sseldorf"
83
+ 4 "___" "n-a"
84
+ 5 "alpha beta gamma delta epsilon zeta eta theta" "alpha-beta-gamma-delta-epsilon-zeta-eta"
85
+ 6 "foo_bar" "foo-bar"
86
+
87
+ Worked notes:
88
+ - Row 3: 'é', '_', 'ü' are all separators (C2). "café_" -> "caf" + one hyphen (the run
89
+ "é_" collapses per C3); "ü" -> one hyphen. Result "caf-d-sseldorf".
90
+ - Row 5: the collapsed slug is 45 chars ("alpha-beta-gamma-delta-epsilon-zeta-eta-theta").
91
+ The first 40 chars end in a hyphen ("...zeta-eta-"), which C5 removes, giving 39 chars.
92
+ - Row 6: '_' is a separator (C2), so "foo_bar" -> "foo-bar".
93
+ - id: candidates-a
94
+ type: code
95
+ label: submissions candidate-1, candidate-2, candidate-3
96
+ content: |
97
+ // Each function is a complete, standalone implementation of normalizeSlug.
98
+
99
+ // ---- candidate-1 ----
100
+ function candidate1(input: string): string {
101
+ let s = input.replace(/[A-Z]/g, (c) => c.toLowerCase()); // C1
102
+ s = s.replace(/[^a-z0-9]+/g, '-'); // C2 + C3
103
+ s = s.replace(/^-+/, '').replace(/-+$/, ''); // C4
104
+ if (s.length > 40) s = s.slice(0, 40).replace(/-$/, ''); // C5
105
+ return s.length === 0 ? 'n-a' : s; // C6
106
+ }
107
+
108
+ // ---- candidate-2 ----
109
+ function candidate2(input: string): string {
110
+ let s = input.replace(/[A-Z]/g, (c) => c.toLowerCase());
111
+ s = s.replace(/[^\w]+/g, '-'); // \w is [A-Za-z0-9_]
112
+ s = s.replace(/^-+/, '').replace(/-+$/, '');
113
+ if (s.length > 40) s = s.slice(0, 40).replace(/-$/, '');
114
+ return s.length === 0 ? 'n-a' : s;
115
+ }
116
+
117
+ // ---- candidate-3 ----
118
+ function candidate3(input: string): string {
119
+ let s = input.replace(/[A-Z]/g, (c) => c.toLowerCase());
120
+ s = s.replace(/[^a-z0-9]+/g, '-');
121
+ s = s.replace(/^-+/, '').replace(/-+$/, '');
122
+ if (s.length > 40) s = s.slice(0, 40); // truncates; no trailing-hyphen removal
123
+ return s.length === 0 ? 'n-a' : s;
124
+ }
125
+ - id: candidates-b
126
+ type: code
127
+ label: submissions candidate-4, candidate-5
128
+ content: |
129
+ // ---- candidate-4 ----
130
+ function candidate4(input: string): string {
131
+ let s = input.replace(/[A-Z]/g, (c) => c.toLowerCase());
132
+ s = s.replace(/[^a-z0-9]+/g, '-');
133
+ s = s.replace(/^-+/, '').replace(/-+$/, '');
134
+ if (s.length > 40) s = s.slice(0, 40).replace(/-$/, '');
135
+ return s; // returns s directly, including '' when the result is empty
136
+ }
137
+
138
+ // ---- candidate-5 ----
139
+ function candidate5(input: string): string {
140
+ let s = input.replace(/[A-Z]/g, (c) => c.toLowerCase());
141
+ s = s.replace(/[^a-z0-9]/g, '-'); // no '+': each separator char becomes its own hyphen
142
+ s = s.replace(/^-+/, '').replace(/-+$/, '');
143
+ if (s.length > 40) s = s.slice(0, 40).replace(/-$/, '');
144
+ return s.length === 0 ? 'n-a' : s;
145
+ }
146
+ - id: reviewer-thread
147
+ type: note
148
+ label: code-review discussion on PR #4471
149
+ content: |
150
+ Review thread for the five normalizeSlug submissions (PR #4471).
151
+
152
+ @ravi: "I stepped candidate-2 through the spec. It lowercases (C1), it collapses separator
153
+ runs to a single hyphen (C2/C3 — note the '+' in the regex), it trims the ends (C4), it
154
+ truncates to 40 and strips a trailing hyphen (C5), and it falls back to 'n-a' on empty (C6).
155
+ Every clause is represented, so I lean toward approving candidate-2."
156
+
157
+ @lena: "The bar is not 'every clause is represented somewhere' — it is 'matches the spec on
158
+ every input.' Compare the exact character classes and the exact order of operations before
159
+ approving anything."
160
+
161
+ BUG-88 (filed by QA, not yet triaged to a specific candidate): "One of the five submissions
162
+ returns an empty response body instead of 'n-a' when the input string is entirely
163
+ punctuation (repro input: \"@@@\")." Note: C6 is unconditional and applies regardless of
164
+ whether an all-punctuation input is considered 'realistic'.
165
+ - id: submission-manifest
166
+ type: note
167
+ label: submission manifest and acceptance rule
168
+ content: |
169
+ Submissions under review for normalizeSlug: candidate-1, candidate-2, candidate-3,
170
+ candidate-4, candidate-5. This is the COMPLETE set — there is no candidate-6 and no other
171
+ implementation is being considered.
172
+
173
+ Acceptance rule: exactly one submission is to be accepted. A submission is accepted only if
174
+ it conforms to normalizer-spec on every input (it reproduces all six rows of test-vectors and
175
+ obeys clauses C1..C7 generally). At most one of the five satisfies this; the acceptance
176
+ process assumes precisely one conformer exists in the set.
177
+ tags: [generation, spec-conformance, string-processing, code-review]
@@ -0,0 +1,208 @@
1
+ id: api-migration-http-retry-client
2
+ version: 1.0.0
3
+ category: refactoring
4
+ cluster: api-migration
5
+ difficulty: expert
6
+ title: Migrating retryFetch call sites to the http.send client at Kelvin Freight
7
+ summary: Six call sites are being moved from the legacy retryFetch helper to the new http.send client. The two APIs look alike but differ in attempt counting, body serialization, return shape, and error type. Classify each migrated call site as behavior-preserving or changes-behavior, citing the exact mismatch.
8
+ prompt: |
9
+ The platform team at Kelvin Freight is retiring the legacy `retryFetch` helper in favor
10
+ of the new `http.send` client. A junior engineer has proposed six migrated call sites
11
+ (proposed-migrations M1..M6), each replacing an original in call-sites (C1..C6).
12
+
13
+ Answer each deliverable using ONLY the supplied artifacts (legacy-api, new-api,
14
+ mapping-note, call-sites, proposed-migrations). Cite the artifact id(s) for every claim.
15
+
16
+ For each migrated call site below, classify it as exactly one of:
17
+ - behavior-preserving (the rewrite produces the same observable behavior as the original)
18
+ - changes-behavior (the rewrite alters observable behavior; name the single defect)
19
+
20
+ "Observable behavior" includes the value/type returned to callers, the bytes and headers
21
+ sent on the wire, the number of network attempts made, which status codes trigger a retry,
22
+ and which catch branch fires. Assume the servers behave identically for both clients.
23
+
24
+ 1. Classify M1 (the migration of C1).
25
+ 2. Classify M2 (the migration of C2).
26
+ 3. Classify M3 (the migration of C3).
27
+ 4. Classify M4 (the migration of C4).
28
+ 5. Classify M5 (the migration of C5).
29
+ 6. Classify M6 (the migration of C6).
30
+
31
+ Exactly one classification is defensible per deliverable; it is fully determined by the
32
+ artifacts. For every changes-behavior verdict, state the single citable defect and, where
33
+ relevant, a concrete input or condition that witnesses the difference.
34
+ artifacts:
35
+ - id: legacy-api
36
+ type: spec
37
+ label: legacy retryFetch helper — signature, defaults, semantics
38
+ content: |
39
+ // http/legacy.ts — the retryFetch helper being retired.
40
+ // This is the COMPLETE set of options retryFetch accepts; there are no others.
41
+
42
+ function retryFetch<T>(url: string, opts?: {
43
+ method?: string; // default 'GET'
44
+ headers?: Record<string, string>;// default {} (NO header is ever added implicitly)
45
+ body?: string; // RAW request body, sent verbatim. retryFetch does
46
+ // NOT serialize and does NOT set Content-Type. If the
47
+ // caller wants JSON, the caller must JSON.stringify AND
48
+ // set the Content-Type header itself.
49
+ retries?: number; // number of ADDITIONAL attempts after the first.
50
+ // default 2. Total attempts made = 1 + retries.
51
+ timeoutMs?: number; // per-attempt timeout. default 5000
52
+ retryOn?: number[]; // status codes that trigger a retry. default [502,503,504]
53
+ }): Promise<T>;
54
+
55
+ // Return / error semantics:
56
+ // - On a 2xx response, retryFetch parses the body as JSON and RETURNS the parsed value (T).
57
+ // - On a non-2xx whose status is in retryOn, it retries (up to `retries` more attempts).
58
+ // After attempts are exhausted it throws LegacyHttpError with a numeric `.status` field.
59
+ // - On a non-2xx NOT in retryOn, it throws LegacyHttpError immediately (no retry).
60
+ // - LegacyHttpError exposes the failing HTTP status on the property `.status`.
61
+ - id: new-api
62
+ type: spec
63
+ label: new http.send client — signature, defaults, semantics
64
+ content: |
65
+ // http/client.ts — the replacement client. This is the COMPLETE option set for http.send.
66
+
67
+ http.send<T>(req: {
68
+ url: string;
69
+ method?: string; // default 'GET'
70
+ headers?: Record<string, string>;// default {}
71
+ json?: unknown; // when present, http.send JSON.stringifies this value AND
72
+ // sets the header Content-Type: application/json. Mutually
73
+ // exclusive with `body`.
74
+ body?: string; // raw body, sent verbatim, NO header added. Use this (not
75
+ // json) for any payload that is not application/json.
76
+ maxAttempts?: number; // TOTAL attempts including the first. default 3.
77
+ timeoutMs?: number; // per-attempt timeout. default 5000
78
+ retryStatuses?: number[]; // status codes that trigger a retry. default [502,503,504]
79
+ }): Promise<{ data: T; status: number }>;
80
+
81
+ // Return / error semantics:
82
+ // - On a 2xx response, http.send RESOLVES to an envelope { data, status } where `data` is
83
+ // the parsed JSON body and `status` is the numeric HTTP status. Callers must read `.data`.
84
+ // - On a non-2xx whose status is in retryStatuses, it retries up to maxAttempts total, then
85
+ // throws SendError. On a non-2xx not in retryStatuses it throws SendError immediately.
86
+ // - SendError exposes the failing HTTP status on the property `.statusCode` (NOT `.status`).
87
+ - id: mapping-note
88
+ type: note
89
+ label: intended migration rules (platform team)
90
+ content: |
91
+ How to move a retryFetch call to http.send without changing behavior. These rules are
92
+ exhaustive for the differences between the two APIs.
93
+
94
+ 1. RETURN SHAPE. retryFetch returns the parsed body directly; http.send returns
95
+ { data, status }. Every migrated call must read `.data` (e.g. destructure
96
+ `const { data } = await http.send(...)`) to recover the old return value.
97
+
98
+ 2. ATTEMPT COUNT. retryFetch `retries: N` means N attempts AFTER the first, i.e. 1 + N
99
+ total. http.send `maxAttempts: M` is the TOTAL. So the faithful mapping is
100
+ maxAttempts = retries + 1. Copying the number across (maxAttempts = retries) drops one
101
+ attempt.
102
+
103
+ 3. BODY. Use http.send `json:` ONLY when the original set Content-Type: application/json and
104
+ passed body: JSON.stringify(x); replace both with `json: x`. For ANY other body (raw text,
105
+ csv, form data, or a body sent without a Content-Type header) keep `body:` verbatim and
106
+ carry the original headers, because `json:` both re-serializes the value and forces
107
+ Content-Type: application/json.
108
+
109
+ 4. DEFAULTS ALIGN. method ('GET'), timeoutMs (5000), and the retry status set ([502,503,504])
110
+ have identical defaults in both APIs, and http.send's default maxAttempts (3) equals
111
+ retryFetch's default total attempts (1 + default retries 2 = 3). Omitting all of these on
112
+ both sides is behavior-preserving. Only change a default if the original set it explicitly.
113
+
114
+ 5. ERRORS. A catch block that read `LegacyHttpError.status` must read `SendError.statusCode`
115
+ after the switch; the property was renamed.
116
+ - id: call-sites
117
+ type: code
118
+ label: original retryFetch call sites (C1..C6)
119
+ content: |
120
+ // C1 — read a user, render its email
121
+ const u1 = await retryFetch<User>('/users/1');
122
+ renderCard(u1.email);
123
+
124
+ // C2 — read a user, render its email
125
+ const u2 = await retryFetch<User>('/users/2');
126
+ renderCard(u2.email);
127
+
128
+ // C3 — create an order; JSON body with explicit Content-Type; 5 extra retries
129
+ await retryFetch('/orders', {
130
+ method: 'POST',
131
+ headers: { 'Content-Type': 'application/json' },
132
+ body: JSON.stringify(order),
133
+ retries: 5,
134
+ });
135
+
136
+ // C4 — emit an event; JSON body; 2 extra retries; 8s timeout; retry only on 503
137
+ await retryFetch('/events', {
138
+ method: 'POST',
139
+ headers: { 'Content-Type': 'application/json' },
140
+ body: JSON.stringify(evt),
141
+ retries: 2,
142
+ timeoutMs: 8000,
143
+ retryOn: [503],
144
+ });
145
+
146
+ // C5 — ingest a CSV export; raw text body with a text/csv Content-Type
147
+ await retryFetch('/ingest', {
148
+ method: 'POST',
149
+ headers: { 'Content-Type': 'text/csv' },
150
+ body: csvRows, // csvRows is already a CSV string
151
+ });
152
+
153
+ // C6 — charge a card; on HTTP 402 show the payment-required screen, else rethrow
154
+ try {
155
+ await retryFetch('/charge', {
156
+ method: 'POST',
157
+ headers: { 'Content-Type': 'application/json' },
158
+ body: JSON.stringify(chg),
159
+ });
160
+ } catch (e) {
161
+ if (e instanceof LegacyHttpError && e.status === 402) showPaymentRequired();
162
+ else throw e;
163
+ }
164
+ - id: proposed-migrations
165
+ type: code
166
+ label: proposed http.send rewrites (M1..M6)
167
+ content: |
168
+ // M1 — migration of C1
169
+ const { data: u1 } = await http.send<User>({ url: '/users/1' });
170
+ renderCard(u1.email);
171
+
172
+ // M2 — migration of C2
173
+ const u2 = await http.send<User>({ url: '/users/2' });
174
+ renderCard(u2.email);
175
+
176
+ // M3 — migration of C3
177
+ await http.send({
178
+ url: '/orders',
179
+ method: 'POST',
180
+ json: order,
181
+ maxAttempts: 5,
182
+ });
183
+
184
+ // M4 — migration of C4
185
+ await http.send({
186
+ url: '/events',
187
+ method: 'POST',
188
+ json: evt,
189
+ maxAttempts: 3,
190
+ timeoutMs: 8000,
191
+ retryStatuses: [503],
192
+ });
193
+
194
+ // M5 — migration of C5
195
+ await http.send({
196
+ url: '/ingest',
197
+ method: 'POST',
198
+ json: csvRows,
199
+ });
200
+
201
+ // M6 — migration of C6
202
+ try {
203
+ await http.send({ url: '/charge', method: 'POST', json: chg });
204
+ } catch (e) {
205
+ if (e instanceof SendError && e.status === 402) showPaymentRequired();
206
+ else throw e;
207
+ }
208
+ tags: [api-migration, refactoring, http-client, behavior-preservation]
@@ -0,0 +1,187 @@
1
+ id: api-migration-orm-query-builder
2
+ version: 1.0.0
3
+ category: refactoring
4
+ cluster: api-migration
5
+ difficulty: expert
6
+ title: Migrating findAll queries to repo.query at Larkspur Analytics
7
+ summary: Six data-access call sites are moving from the legacy findAll query builder to the new repo.query builder. Null handling, sort-direction casing, pagination argument names, and soft-delete flags differ between the two. Classify each migrated query as behavior-preserving or changes-behavior, citing the exact mismatch.
8
+ prompt: |
9
+ Larkspur Analytics is replacing its legacy `db.findAll` query builder with the new
10
+ `repo.query` builder. A migration PR rewrites six queries (proposed-queries Q1..Q6), each
11
+ replacing an original in legacy-queries (Q1..Q6).
12
+
13
+ Answer each deliverable using ONLY the supplied artifacts (legacy-builder, new-builder,
14
+ mapping-note, legacy-queries, proposed-queries). Cite the artifact id(s) for every claim.
15
+
16
+ For each migrated query below, classify it as exactly one of:
17
+ - behavior-preserving (the rewrite selects the same rows in the same order as the original)
18
+ - changes-behavior (the rewrite alters the result set or ordering; name the single defect)
19
+
20
+ "Same behavior" means the same rows (same filter, same soft-delete inclusion), the same page
21
+ (same limit/offset window), and the same ordering. Assume identical table contents for both
22
+ builders.
23
+
24
+ 1. Classify Q1.
25
+ 2. Classify Q2.
26
+ 3. Classify Q3.
27
+ 4. Classify Q4.
28
+ 5. Classify Q5.
29
+ 6. Classify Q6.
30
+
31
+ Exactly one classification is defensible per deliverable; it is fully determined by the
32
+ artifacts. For every changes-behavior verdict, name the single citable defect and, where
33
+ relevant, the rows or ordering that differ.
34
+ artifacts:
35
+ - id: legacy-builder
36
+ type: spec
37
+ label: legacy db.findAll query builder — options and semantics
38
+ content: |
39
+ // orm/legacy.ts — the findAll builder being retired.
40
+ // This is the COMPLETE option set findAll accepts; there are no others.
41
+
42
+ db.findAll(model, {
43
+ where?: Record<string, unknown>; // field -> value equality. A value of null generates
44
+ // "field IS NULL". Omitting a field adds no constraint.
45
+ limit?: number; // max rows returned. default: no limit
46
+ offset?: number; // rows skipped before the window. default 0
47
+ order?: [string, 'ASC' | 'DESC'][]; // e.g. [['createdAt','DESC']]. default []: the
48
+ // database's natural (unspecified) order.
49
+ include?: string[]; // relations to eager-load. default []
50
+ paranoid?: boolean; // default TRUE. When true, findAll AUTOMATICALLY adds
51
+ // "deletedAt IS NULL", i.e. soft-deleted rows are excluded.
52
+ // false -> soft-deleted rows are included.
53
+ }): Promise<Row[]>
54
+ - id: new-builder
55
+ type: spec
56
+ label: new repo.query builder — options and semantics
57
+ content: |
58
+ // orm/repo.ts — the replacement builder. This is the COMPLETE option set for repo.query.
59
+
60
+ repo.query({
61
+ model: Model;
62
+ filter?: Record<string, unknown>;// field -> value equality. IMPORTANT: a value of null is
63
+ // IGNORED (treated as "no constraint on this field"). To
64
+ // match SQL NULL you must write { field: { is: null } }.
65
+ take?: number; // max rows returned. default: no limit
66
+ skip?: number; // rows skipped before the window. default 0
67
+ sort?: Record<string, 'asc' | 'desc'>; // directions are LOWERCASE only. An unrecognized
68
+ // value (including 'ASC'/'DESC') is treated as 'asc'.
69
+ // default {}: natural order.
70
+ with?: string[]; // relations to eager-load. default []
71
+ withDeleted?: boolean; // default FALSE -> soft-deleted rows excluded
72
+ // ("deletedAt IS NULL" added automatically). true ->
73
+ // soft-deleted rows included.
74
+ }): Promise<Row[]>
75
+ - id: mapping-note
76
+ type: note
77
+ label: intended migration rules (data platform team)
78
+ content: |
79
+ How to move a findAll query to repo.query without changing the result set. These rules are
80
+ exhaustive for the differences between the two builders.
81
+
82
+ 1. WHERE / FILTER NULLS. `where` and `filter` are the same for non-null values. But
83
+ where: { f: null } means "f IS NULL", while filter: { f: null } is IGNORED and matches
84
+ every row. Translate any null in `where` to `filter: { f: { is: null } }`.
85
+
86
+ 2. PAGINATION. limit -> take, offset -> skip (identical meaning). Keep the numbers with their
87
+ keys; do not swap take and skip.
88
+
89
+ 3. SORT. order [['f','DESC']] -> sort { f: 'desc' } (and 'ASC' -> 'asc'). The direction MUST
90
+ be lowercased: repo.query treats 'DESC'/'ASC' as unrecognized and silently falls back to
91
+ 'asc'. A single order tuple becomes a single sort key.
92
+
93
+ 4. SOFT DELETE. findAll paranoid: true (the default) == repo.query withDeleted: false (the
94
+ default): both exclude soft-deleted rows. paranoid: false == withDeleted: true (include
95
+ soft-deleted). If the original set paranoid: false, the migration MUST set
96
+ withDeleted: true; omitting it flips soft-deleted rows back out of the result set.
97
+
98
+ 5. RELATIONS. include -> with (identical). Default [] on both sides; omitting is safe.
99
+ - id: legacy-queries
100
+ type: code
101
+ label: original findAll queries (Q1..Q6)
102
+ content: |
103
+ // Q1 — active members of org 5, newest first, page 3 (20 per page), with profile
104
+ db.findAll(Member, {
105
+ where: { orgId: 5 },
106
+ limit: 20,
107
+ offset: 40,
108
+ order: [['createdAt', 'DESC']],
109
+ include: ['profile'],
110
+ });
111
+
112
+ // Q2 — sessions that have NOT been revoked (revokedAt is null)
113
+ db.findAll(Session, {
114
+ where: { revokedAt: null },
115
+ });
116
+
117
+ // Q3 — posts newest first
118
+ db.findAll(Post, {
119
+ order: [['publishedAt', 'DESC']],
120
+ });
121
+
122
+ // Q4 — audit accounts for owner 9, INCLUDING soft-deleted ones
123
+ db.findAll(Account, {
124
+ where: { ownerId: 9 },
125
+ paranoid: false,
126
+ });
127
+
128
+ // Q5 — tokens for user 3 that never expire (expiresAt is null), including revoked/deleted,
129
+ // oldest id first
130
+ db.findAll(Token, {
131
+ where: { userId: 3, expiresAt: null },
132
+ paranoid: false,
133
+ order: [['id', 'ASC']],
134
+ });
135
+
136
+ // Q6 — a page of logs: 10 rows starting after the first 100
137
+ db.findAll(Log, {
138
+ limit: 10,
139
+ offset: 100,
140
+ });
141
+ - id: proposed-queries
142
+ type: code
143
+ label: proposed repo.query rewrites (Q1..Q6)
144
+ content: |
145
+ // Q1
146
+ repo.query({
147
+ model: Member,
148
+ filter: { orgId: 5 },
149
+ take: 20,
150
+ skip: 40,
151
+ sort: { createdAt: 'desc' },
152
+ with: ['profile'],
153
+ });
154
+
155
+ // Q2
156
+ repo.query({
157
+ model: Session,
158
+ filter: { revokedAt: null },
159
+ });
160
+
161
+ // Q3
162
+ repo.query({
163
+ model: Post,
164
+ sort: { publishedAt: 'DESC' },
165
+ });
166
+
167
+ // Q4
168
+ repo.query({
169
+ model: Account,
170
+ filter: { ownerId: 9 },
171
+ });
172
+
173
+ // Q5
174
+ repo.query({
175
+ model: Token,
176
+ filter: { userId: 3, expiresAt: { is: null } },
177
+ withDeleted: true,
178
+ sort: { id: 'asc' },
179
+ });
180
+
181
+ // Q6
182
+ repo.query({
183
+ model: Log,
184
+ take: 100,
185
+ skip: 10,
186
+ });
187
+ tags: [api-migration, refactoring, orm, query-builder, behavior-preservation]