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.
- package/README.md +141 -173
- package/dist/{chunk-UXFKTSQS.js → chunk-AY45YLYL.js} +105 -4
- package/dist/{chunk-MBC4C3VA.js → chunk-CJGHBY54.js} +7 -6
- package/dist/{chunk-3CX4FIFZ.js → chunk-DVMGL3L7.js} +80 -5
- package/dist/{chunk-KGZEQ6R5.js → chunk-IUPFMGUL.js} +152 -15
- package/dist/{chunk-XPFZBHV2.cjs → chunk-KCXQ5SAU.cjs} +21 -20
- package/dist/{chunk-SMG3PORS.cjs → chunk-QMOPRKWD.cjs} +89 -14
- package/dist/{chunk-5OZGHLDH.cjs → chunk-VAS6KNJA.cjs} +216 -79
- package/dist/{chunk-5PCDG7VA.cjs → chunk-X3LPZGHS.cjs} +106 -5
- package/dist/cli.cjs +51 -48
- package/dist/cli.js +9 -6
- package/dist/client.cjs +5 -5
- package/dist/client.d.cts +3 -3
- package/dist/client.d.ts +3 -3
- package/dist/client.js +4 -4
- package/dist/contracts/index.cjs +6 -2
- package/dist/contracts/index.d.cts +2 -2
- package/dist/contracts/index.d.ts +2 -2
- package/dist/contracts/index.js +7 -3
- package/dist/index.cjs +8 -4
- package/dist/index.d.cts +9 -8
- package/dist/index.d.ts +9 -8
- package/dist/index.js +8 -4
- package/dist/{logger-QJU7SBDz.d.ts → logger-BByta-7V.d.cts} +23 -23
- package/dist/{logger-CCR9Mg1c.d.cts → logger-BQf29BLe.d.ts} +23 -23
- package/dist/{reports-4CejmOHf.d.cts → reports-B8TCJtPr.d.ts} +57 -13
- package/dist/{reports-s2CTnGN8.d.ts → reports-DPpOoOux.d.cts} +57 -13
- package/dist/{tasks-CpaCJ6JE.d.cts → tasks-BmhWuMBD.d.cts} +24 -22
- package/dist/{tasks-CpaCJ6JE.d.ts → tasks-BmhWuMBD.d.ts} +24 -22
- package/dist/tasks.cjs +3 -3
- package/dist/tasks.d.cts +1 -1
- package/dist/tasks.d.ts +1 -1
- package/dist/tasks.js +2 -2
- package/docs/README.md +32 -12
- package/docs/methodology.md +14 -0
- package/docs/operator-guide.md +178 -0
- package/docs/replay-elo.md +12 -2
- package/docs/reviewing-bridgebench.md +173 -0
- package/docs/task-authoring.md +79 -1
- package/package.json +5 -3
- package/tasks/bullshit/public/crossed-metric-properties.yaml +192 -0
- package/tasks/bullshit/public/crossed-release-checksums.yaml +199 -0
- package/tasks/bullshit/public/fabricated-config-keys.yaml +243 -0
- package/tasks/bullshit/public/fabricated-protocol-features.yaml +272 -0
- package/tasks/bullshit/public/impossible-capacity-math.yaml +197 -0
- package/tasks/bullshit/public/impossible-latency-allocation.yaml +215 -0
- package/tasks/bullshit/public/loaded-approval-bypass.yaml +229 -0
- package/tasks/bullshit/public/loaded-migration-fallout.yaml +221 -0
- package/tasks/bullshit/public/pseudo-gc-heap-tuning.yaml +206 -0
- package/tasks/bullshit/public/pseudo-network-tuning.yaml +204 -0
- package/tasks/bullshit/public/reversed-alert-cascade.yaml +251 -0
- package/tasks/bullshit/public/reversed-dependency-failure.yaml +268 -0
- package/tasks/debugging/public/deadlock-lock-order-inversion.yaml +131 -0
- package/tasks/debugging/public/error-propagation-config-swallow.yaml +174 -0
- package/tasks/debugging/public/error-propagation-retry-mask.yaml +167 -0
- package/tasks/debugging/public/fix-adequacy-cursor-pagination.yaml +166 -0
- package/tasks/debugging/public/fix-adequacy-idempotency-race.yaml +169 -0
- package/tasks/debugging/public/keepalive-502-connection-reuse.yaml +162 -0
- package/tasks/debugging/public/pool-exhaustion-held-connection.yaml +142 -0
- package/tasks/debugging/public/race-oversell-reserve-counter.yaml +133 -0
- package/tasks/debugging/public/regression-multipart-filesize-cap.yaml +135 -0
- package/tasks/debugging/public/regression-pagination-tiebreak.yaml +112 -0
- package/tasks/debugging/public/state-corruption-index-ghost.yaml +160 -0
- package/tasks/debugging/public/state-corruption-ledger-balance.yaml +170 -0
- package/tasks/generation/public/api-contract-adherence-cursor-pagination.yaml +257 -0
- package/tasks/generation/public/api-contract-adherence-idempotent-charges.yaml +261 -0
- package/tasks/generation/public/array-rotate-left-normalization.yaml +166 -0
- package/tasks/generation/public/cache-interface-dropin.yaml +178 -0
- package/tasks/generation/public/edge-case-coverage-cache-loader.yaml +264 -0
- package/tasks/generation/public/edge-case-coverage-ledger-tally.yaml +231 -0
- package/tasks/generation/public/event-envelope-wire-compat.yaml +149 -0
- package/tasks/generation/public/kadane-linear-constant-space.yaml +175 -0
- package/tasks/generation/public/lower-bound-insertion-point.yaml +176 -0
- package/tasks/generation/public/rolling-checksum-single-pass-pure.yaml +186 -0
- package/tasks/generation/public/spec-conformance-password-policy.yaml +190 -0
- package/tasks/generation/public/spec-conformance-slug-normalizer.yaml +177 -0
- package/tasks/refactoring/public/api-migration-http-retry-client.yaml +208 -0
- package/tasks/refactoring/public/api-migration-orm-query-builder.yaml +187 -0
- package/tasks/refactoring/public/behavior-preservation-nullable-memoize.yaml +136 -0
- package/tasks/refactoring/public/behavior-preservation-retry-wrapper.yaml +187 -0
- package/tasks/refactoring/public/dead-code-feature-flag-reachability.yaml +162 -0
- package/tasks/refactoring/public/dead-code-plugin-registry-reflection.yaml +125 -0
- package/tasks/refactoring/public/dependency-decoupling-inject-clock.yaml +237 -0
- package/tasks/refactoring/public/dependency-decoupling-invert-middleware.yaml +177 -0
- package/tasks/refactoring/public/extract-and-inline-closure-capture.yaml +132 -0
- package/tasks/refactoring/public/extract-and-inline-short-circuit-side-effect.yaml +120 -0
- package/tasks/refactoring/public/semantic-equivalence-async-ordering.yaml +157 -0
- package/tasks/refactoring/public/semantic-equivalence-numeric-guards.yaml +115 -0
- package/tasks/security/public/authz-guard-chain-exposure.yaml +224 -0
- package/tasks/security/public/authz-object-scope-idor.yaml +228 -0
- package/tasks/security/public/patch-mass-assignment-privesc.yaml +226 -0
- package/tasks/security/public/patch-sqli-candidate-fixes.yaml +243 -0
- package/tasks/security/public/supply-lockfile-tamper-trace.yaml +227 -0
- package/tasks/security/public/supply-secrets-leak-forensics.yaml +205 -0
- package/tasks/security/public/taint-export-template-render.yaml +207 -0
- package/tasks/security/public/taint-webhook-outbound-fetch.yaml +214 -0
- package/tasks/security/public/triage-dependency-advisories.yaml +189 -0
- package/tasks/security/public/triage-sast-false-positives.yaml +257 -0
- package/tasks/security/public/vuln-path-sanitizer-escape.yaml +165 -0
- package/tasks/security/public/vuln-token-alg-confusion.yaml +252 -0
- package/tasks/speed/public/speed-api-diff-changelog.yaml +42 -0
- package/tasks/speed/public/speed-callbacks-to-async.yaml +39 -0
- package/tasks/speed/public/speed-class-to-hooks.yaml +47 -0
- package/tasks/speed/public/speed-config-schema-json.yaml +34 -0
- package/tasks/speed/public/speed-csv-report-generator.yaml +38 -0
- package/tasks/speed/public/speed-debounce-hook.yaml +28 -0
- package/tasks/speed/public/speed-log-triage-summary.yaml +35 -0
- package/tasks/speed/public/speed-openapi-fragment.yaml +38 -0
- package/tasks/speed/public/speed-pagination-cursor.yaml +40 -0
- package/tasks/speed/public/speed-rate-limiter-design.yaml +43 -0
- package/tasks/speed/public/speed-rest-client-module.yaml +42 -0
- 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]
|