Repository navigation
refactor: return Result from parquet merge and read instead of throwing - #174
sunwrobert wants to merge 2 commits into
Conversation
|
I'll fix CI failures and address comments from users with write access that start with 'Devin'.
Original prompt from Devin Bot
|
There was a problem hiding this comment.
✅ Approved. The Parquet read/merge APIs now return typed Results for expected runtime-data failures, and the in-repository callers handle those Results. No actionable correctness regressions found.
Scope and evidence
Reviewed all five changed files at cc63161, against actual base c6a05aa324948d4fd66b89c2c7e8b9a3065ee01a, with two independent correctness passes. Examined the two-pass merge checks, reader and writer failure propagation, Parquet decoding and row validation, scored-column JSON/schema parsing, error-message formatting, CLI handling, and migrated test helpers. Candidate concerns about reader API composition and unchecked Results in the streaming test were independently checked and did not establish reachable defects.
Risk and boundaries
The public return types intentionally change, so consumers must unwrap Results rather than use rows or merge metadata directly. The in-repository consumers were checked; the external monorepo subtree migration was not available in this checkout and is not verified by this approval. Successful serialization and merge-consistency guards remain in place. Third-party writer internals were not available for inspection, so this review does not establish that every possible dependency exception is converted to a Result.
Verification
Static review only: no PR code, tests, installs, or builds were run. Inspected the changed round-trip/streaming assertions, changed-between-passes cases, typed error cases, and message-formatting coverage; the author's reported test results were not independently reproduced. CI was not checked.
TL;DR
mergeResultFilesToParquet,readResultRowsandparseJsonColumninsrc/results/parquet.tsreturnResult<T, ParquetError>(effect/Result) instead of throwing on expected runtime-data failures;parquetErrorMessage()reproduces every previous message byte for byte, so callers see the same text.throw-to-result: src/results/parquet.ts (vendored in openrouter-web as
packages/bench-harness/src/results/parquet.ts)remaining (harness,
git grep -E "^\s*throw new Error\(" -- 'src/**/*.ts' ':!*.test.ts' | grep -vE "// invariant:" | wc -l): 60 → 56remaining (openrouter-web,
git grep -E "^\s*throw new Error\(" -- 'packages/**/*.ts' 'services/**/*.ts' ':!*.test.ts' ':!*.integration.test.ts' | grep -vE "// invariant:" | wc -lon main): 1298 → 1298 until the subtree-pull PR lands, then 1294Result convention: this package uses
effect/Result(Result.succeed/fail/isFailure,src/internal/result.tsAsyncResult/tryPromiseResult), not@openrouter-monorepo/lib-result; the code-scan profile names lib-result, the package convention wins.Sibling: the openrouter-web PR (subtree pull + caller updates in
services/gcp-harbor-run/src/publish-kepler-run.ts,services/gcp-harbor-run/src/result-rows.ts,packages/temporal/src/activities/benchmark/import-existing-chunks.ts) opens after this merges;scripts/check-subtree-integrity.tsrejects any direct edit to the vendored copy.What changed?
ParquetErrorunion, onekindper failure, carrying only the interpolated data:mergeResultFilesToParquet(): AsyncResult<MergedResultFiles, ParquetError>:readFile()rejections, the changed-between-passes check, andParquetWriter.write/finishrejections becomeResult.fail(...);scanResultFilesand the per-rowtoSampleScorescallback return Results and short-circuit on the first failure.readResultRows(): AsyncResult<readonly BenchmarkResultRow[], ParquetError>:parquetReadObjectsrejections (parquet-decode-failed) and Zod row failures (invalid-result-row).parseJsonColumn(): Result<T | undefined, ParquetError>:JSON.parsewrapped withResult.try(invalid-result-column-json), Zod failures (invalid-result-column);rowsToScoredSamplescollects per-row Results withResult.allinstead of letting the first throw escape the.map.parquetErrorMessage(error): string: exhaustive switch producing the exact previous strings;causekinds go throughunknownErrorToString(src/internal/errors.ts), which is the same function the monorepo callers applied to the wrapped exception.cellValuedefault:return name satisfies never;instead ofthrow new Error("Unhandled column"). The requested// invariant: exhaustive ColumnName switchmarker cannot be added in this repo:openrouter/no-comments(oxlint,tools/oxlint/no-comments.js) rejects every non-directive comment, and the function already returnsunknown, so the throw is not needed.src/benchmarks/toolcall-schema-fuzz/cli/report.ts: branches on the Result and writesparquetErrorMessage()to stderr with exit 1.rejects.toThrow("Result file 0 changed between merge passes")cases becameassertFailure+toEqual({ kind: "result-file-changed", index: 0 })with the same inputs; one new test perkindplusparquetErrorMessageformatting tests;readRows/mergeToBytes/resultRowshelpers andori-parquet.test.tsnarrow withassertSuccess.Callers (all in this repo):
src/benchmarks/toolcall-schema-fuzz/cli/report.ts,src/results/parquet.test.ts,src/results/parquet-stream-writer.test.ts,src/benchmarks/terminal-bench/ori-parquet.test.ts. Monorepo callers (sibling PR):services/gcp-harbor-run/src/publish-kepler-run.ts,services/gcp-harbor-run/src/result-rows.ts,packages/temporal/src/activities/benchmark/import-existing-chunks.ts.purely mechanical: no
toolcall-schema-fuzz/cli/report.ts: an unreadable results file used to end in Bun's uncaught-rejection stack dump on stderr; it now prints the one-lineparquetErrorMessage()on stderr. Exit code 1 in both cases.readFile()reader or aWritersink that rejects used to reject themergeResultFilesToParquetpromise; it now resolves toResult.fail({ kind: "result-file-read-failed" | "parquet-write-failed", cause }). Monorepo callers wrapped the promise, so their output is unchanged (Kepler result merge failed: <unknownErrorToString(cause)>both before and after).cellValuedefault no longer throws (unreachable:name satisfies never).// invariant:markers added: none (repo lint bans comments; the one candidate site was removed instead).Prod impact: none until the openrouter-web subtree pull. Worst case after that: a
ParquetErrorkind formats a different string than the old exception message, whichparquetErrorMessagetests pin for every kind; the Kepler publish and result-rows messages are asserted unchanged in the monorepo tests (publish-kepler-run.test.ts,result-rows.test.ts).Why?
Code-scan finding sfind-1710b4727c8d4d5096e41abab2a6114e: expected data failures were exceptions, so every caller caught generically with
wrap()and stringified; the type system could not show which failures a caller handled. AGENTS.md: "Preserve the repository's strict TypeScript and Effect patterns."How to test
Show me
Visualization
Artifact: https://openrouter.devinenterprise.com/api/presigned_proxy?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjpudWxsLCJidWNrZXRfbmFtZSI6ImRldmluYXR0YWNobWVudHMiLCJidWNrZXRfa2V5IjoiYXR0YWNobWVudHNfcHJpdmF0ZS9jbGVyay1vcmdfMmhUSzBmbWdTSk80ZncwT041T1NwOUtFZG1iL2QzMDk3OTdlLTNjM2EtNGEzNi1iYzAxLWEwZWJhZjM2OTQ0OCIsImlhdCI6MTc5MzQ4MzI5NSwib3JnX2lkIjoiY2xlcmstb3JnXzJoVEswZm1nU0pPNGZ3ME9ONU9TcDlLRWRtYiIsImZpbGVuYW1lIjoidGhyb3ctdG8tcmVzdWx0LXBhcnF1ZXQtMjAyNi0xMS0wMS5odG1sIn0.wZRRAqoF0DHrvG2jp4G4VINIfp5vOGZ0T-NJXSwWCQY
Proof matrix
Base =
origin/mainc6a05aa in a worktree with the head'ssrc/results/parquet.test.tsandparquet-stream-writer.test.tschecked out; head = cc63161. Command on both:bun test src/results/parquet.test.ts src/results/parquet-stream-writer.test.ts. On base the head test file fails at module load (parquetErrorMessageis not exported there), so the base column was run with that import patched toString(e); everything else is identical test code.result-file-changedparquet.test.ts› "rejects a file whose row count / scores / run-level usage changes between passes" (3). Base: fail, uncaughtResult file 0 changed between merge passes. Head: pass,toEqual({ kind: "result-file-changed", index: 0 })result-file-read-failedParquetError › returns result-file-read-failed when a reader rejects. Base: fail, uncaughtdisk gone. Head: pass, messagedisk goneparquet-write-failedParquetError › returns parquet-write-failed when the writer sink rejects. Base: fail, uncaughtupload closed. Head: passparquet-decode-failedParquetError › returns parquet-decode-failed for bytes that are not parquet. Base: fail (module load, see note). Head: pass,parquet file invalid (footer != PAR1)invalid-result-rowParquetError › returns invalid-result-row for parquet without the result columns. Base: fail (module load). Head: pass, starts withInvalid benchmark result row:invalid-result-column-jsonParquetError › returns invalid-result-column-json when a scored JSON column is not JSON. Base: fail, uncaughtSyntaxError. Head: pass, starts withJSON Parse errorinvalid-result-columnParquetError › returns invalid-result-column when a scored JSON column fails its schema. Base: fail, uncaughtInvalid result column: .... Head: passParquetError › formats result-file-changed with the file index,formats a non-Error cause the way unknownErrorToString does; all 34 pre-existingrunResultToParquet/mergeResultFilesToParquet/summarizeChunkRowsround-trip tests pass on head with their original assertions (byte-identical parquet output,parquetStreamWriterbyte comparison)Unchanged-input equivalence:
bun testfull suite 1837 pass / 0 fail on head (1837 on main).Thermo-nuclear fix loop
Round 1 (97e7751): three near-identical "tryPromiseResult then rebuild the failure" ternaries (
readResultFile,writeParquetRows, finish) → collapsed ontoResult.mapErrorand onetryParquetWrite(step);SampleScoresOfalias (single use) inlined;rowsToUnscoredSampleswrapper inlined at its only call site. Fixed in cc63161.Round 2 (cc63161): no findings. File is 919 lines (was 843), no new conditionals in shared paths, no casts, no
any.Deslop-micro (fix mode) on cc63161: inventories
ParquetError,parquetErrorMessage,readResultFile,writeParquetRows,tryParquetWrite,rowToScoredSample— allkeep(names match the module's existingwriteParquet/readResultRowsvocabulary;tryParquetWritemirrorstryPromiseResult). Invented terms removed in round 1 (SampleScoresOf,rowsToUnscoredSamples). No separate deslop commit.Benchmark impact
None: no solver, scorer, prompt or dataset change; merged parquet bytes are unchanged (
parquetStreamWriterbyte-equality test).Reviewer focus
parquetErrorMessagestrings against the removedthrow new Error(...)texts.Result.all(rows.map(rowToScoredSample))reports the first failing row in row order, as the old throw-in-.mapdid.readFile()and theWritersink: this keeps the monorepomergeKeplerResultsoutput identical once it dropswrap().Checklist
mergeResultFilesToParquet/readResultRowschange; the only external consumer is openrouter-web, updated in the sibling PR)Link to Devin session: https://openrouter.devinenterprise.com/sessions/c4ce6b7cd05344d3bd734eb3233e6935
Open in Devin Desktop: https://openrouter.devinenterprise.com/desktop/session/c4ce6b7cd05344d3bd734eb3233e6935?variant=devin
Requested by: @sunwrobert