Conversation
jev4k now builds from common code for the JVM, Apple platforms, Linux, Windows and Node.js (js and wasmJs), with a default Ktor engine per platform. On the JVM, the public API, dependencies, bytecode level and behavior are unchanged from 0.1.0. - Breaking: the Maven group is now com.pambrose.jev4k. Gradle builds depend on com.pambrose.jev4k:jev4k, Maven builds on com.pambrose.jev4k:jev4k-jvm; 0.1.0 stays at com.pambrose:jev4k. The release notes, README and docs site call this out. - ABI validation guards the JVM and klib surfaces (api/). - The common tests run on every platform; LiveProbeTest adds live checks that spend no tokens. - make docker-linux-tests runs the linuxX64 and linuxArm64 tests in Docker containers, and make platform-tests runs every platform's tests. The tvOS and watchOS simulator tests run where a simulator device exists. - CI adds macOS and Windows jobs. - Fix four empty admonitions and a broken anchor on the docs site. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Replace defaultHttpClient(timeoutMillis) and DEFAULT_ENGINE_NAME with one defaultEngine per platform. CIO ignores its own requestTimeout whenever HttpTimeout is installed, which jev4k always does, so the argument had no effect; JevConfig.toString names the engine's class. - Share the bare-IllegalStateException rule for Curl and WinHttp in nativeMain, and the test fixtures (PAYOUT_TICKET, triageJev, testDefaults, liveOptIn) in TestSupport. - Read the simulator listing through a ValueSource that keeps only the platforms with a device, so a simulator run no longer discards the configuration cache; disable link tasks through KGP's binaries API. - Makefile: HOST_TESTS, DOCKER_UP and LIVE_PROBE_TESTS replace repeated text; live-tests filters every platform to LiveProbeTest. - CI: merge the native jobs into one matrix job, key the ~/.konan cache on the Kotlin version without restore-keys, and make docs.yml restore-only so Dokka can't save an incomplete toolchain. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…view defaultEngine = CIO was a stored top-level value in Platform.jvm.kt, so the Platform_jvmKt class initializer loaded CIO. platformGetenv lives in the same class and runs whenever a setting comes from the environment, so a consumer who supplied an engine and excluded ktor-client-cio, as the README suggests, got NoClassDefFoundError before any request. defaultEngine is now a getter in its own Engine.jvm.kt, and CioExclusionTest builds a config in a class loader that hides CIO (it failed before the change). Add docs/code-review-2026-09-27.md: 85 verified issues with a checkbox summary and a fix order. This change fixes item 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WQjZxyKov2izXjyWbYdCc5
Step 2 of docs/code-review-2026-09-27.md (items 13, 14, 15, 50, 58, 59, 61, 69 and 70): - docs.yml builds the site and KDocs on every PR and master push with Zensical --strict, and deploys only when a release is published or on a manual run, so jev4k.com never shows a version that isn't on Central yet. The github-pages environment now also accepts release tags. - ci.yml gains a ci-ok job that passes only when every other job succeeded; branch protection on master now requires ci-ok, docs and GitGuardian. The Apple row also runs the tvOS and watchOS simulator tests. - Gradle's failOnNoDiscoveredTests guard is switched back on after the Kotest plugin turns it off, so a test binary that lost its specs fails the build. - make build also compiles the JVM test sources (the doc examples and JavaInterop.java). - The release checklist publishes to Central from the release PR's branch before merging, names GettingStarted.txt in its version steps, and drops its stale intro and line reference. - The README's MockK fixture uses the enum's actual option key, TECHNICAL. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WQjZxyKov2izXjyWbYdCc5
Welcome to Codecov 🎉Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests. Thanks for integrating Codecov - We've got you covered ☂️ |
…ntact Step 3 of docs/code-review-2026-09-27.md (items 2, 3, 4, 5, 6, 8, 23, 53, 55 and 71): - The response body is read as raw bytes and decoded leniently, so a malformed Content-Type or bytes that aren't UTF-8 no longer escape as Ktor exceptions. A leading byte-order mark is dropped. - A body cut short of its Content-Length (Ktor's own check, on the JVM and native targets) is a connection error on every platform. On the JVM, an untrusted TLS certificate and a response CIO can't parse are too. - A call on a closed client fails fast with IllegalStateException. A call cancelled because a sibling coroutine failed ends with its own CancellationException, and retriesOn follows Ktor's rule of retrying a cancellation only when it wraps a timeout. - JSON nested more than 512 levels deep, in a state, a question entry or a response, is rejected instead of overflowing the stack. - With a supplied engine, a timeout from that engine's own connect or socket timeout is named in the message instead of quoting config.timeout. - The release notes, CHANGELOG, README and errors page describe the Linux and Windows bare-IllegalStateException rule. New common tests run on every platform; ClientJvmTest drives a real CIO engine against RawServer for the TLS, truncated-body and garbled-response cases, which also pins Ktor's wording. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WQjZxyKov2izXjyWbYdCc5
Encoding a state nested 512 levels deep crashed the mingwX64 test binary in CI: kotlinx.serialization recurses several frames per level, and Windows gives the main thread a 1 MB stack. MAX_JSON_DEPTH is now 128 (serde_json's default), a quarter of the stack that overflowed and still far deeper than any real state or answer. The tests use the constant, so they check at and just over the new limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WQjZxyKov2izXjyWbYdCc5
Step 4 of docs/code-review-2026-09-27.md (items 9, 10, 21, 28, 29 and 30): - String settings are trimmed, and a value that is blank after trimming counts as unset, so an API key or URL read from a file with a trailing newline works. - JevConfigBuilder.build() rejects what Ktor would otherwise refuse on every request with a raw exception quoting the value: an API key with a control character (which leaked the whole key), an invalid header name or value, and a base URL that doesn't parse. A base URL with credentials, a query or a fragment is rejected too. No message quotes the key, a header value, or a URL that could hold credentials. - Plain http:// is accepted only for a loopback host unless the new JevConfigBuilder.allowInsecureHttp is set. The ABI dumps record the addition, and the release notes, README and Installation page say it is the one other change that can break code written for 0.1.0. - The retry policy keeps its own copy of the status set, and a blank per-call model falls back to the configured default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WQjZxyKov2izXjyWbYdCc5
…izable errors Step 5 of the code review (docs/code-review-2026-09-27.md), batched into one ABI update: - JevCallOptions overrides a call's timeout, retry policy and headers and can add top-level body fields, through new JevApi members with default implementations and JevApi.withOptions (#27). Headers are now set on each request: Ktor's DefaultRequest merges its headers with a request's own, so a per-call header would have been sent twice. - models() returns a ModelList, a List<ModelInfo> carrying the request id (#20). - Every Duration setting has a millisecond twin for Java, RetryPolicy gains a with... method per setting, jevApiException takes headers before retryAfter and reads the hint from them, and jevResult has @jvmoverloads (#12). - BlockingJev's methods declare InterruptedException (#42), and JevApi.blocking() wraps any JevApi (#44). - JevApiException lowercases header names (#7) and, like JevRateLimitException, holds only Serializable state (#43). - ValueJson loses its needless @PublishedApi (#46); the typed builders' argument order (#40) and the sealed-subtype policy (#41) are documented. The changelog and release notes list the few source-level changes from 0.1.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WQjZxyKov2izXjyWbYdCc5
…s fully Step 6 of the code review (docs/code-review-2026-09-27.md): - enumChoice<E>(id) requires E to cover every declared option, a caller mistake reported as IllegalArgumentException instead of a response error (#11). - A null answer counts as absent (#17); a non-finite number or a Score level outside the question's levels is a response error with its path (#18). - Mapping errors carry the body as received, the status and the headers, through an internal ResponseInfo that send builds once (#19). - A number or boolean state is rejected, and instructions must be non-blank text or a non-empty object or array (#31); a question entry holding NaN or an infinity is reported by validation (#32); jsonOf converts primitive arrays (#33). - A JevValidationException inside a builder lambda is carried on the question instead of escaping a JevQuery's initializer, and the question's shape isn't checked, so no knock-on count is reported (#34). - A JevQuery rebuilds its set when questions register after a read (#35); QuestionSet.ids reads the set's own copy (#36); question(id, q) copies its map or list and checks the answer's type (#37, #38). - An undeclared option in a string-keyed Choice is returned as sent, as both official SDKs do; that policy is now documented and tested (#39). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WQjZxyKov2izXjyWbYdCc5
… body cap Step 7 of the code review (docs/code-review-2026-09-27.md): - Redirects are reported, not followed (#25). Ktor strips only Authorization on a cross-host redirect, and fetch re-sends the POST body; every default engine leaves redirects to Ktor, so followRedirects = false covers them all. - ContentNegotiation uses SkipIfPresent, so a configured Accept is the only one sent (#24). - A Retry-After HTTP-date is honored, measured from an internal clock and capped by maxRetryAfter, as the Python SDK does (#22). - A response declaring a body over 16 MiB is refused before Ktor reads it, from a receive-pipeline phase ahead of SaveBody: a 2xx as a response error, any other status as its usual exception with no body (#26). A chunked body stays bounded only by the timeout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WQjZxyKov2izXjyWbYdCc5
…ter mocks
Step 8 of the code review (docs/code-review-2026-09-27.md):
- jevResult(String) checks the body with the client's own parser, now
ResponseInfo.parseObject(), so non-JSON text is a JevResponseValidationException (#48).
- A handle from another QuestionSet with a matching id gets a message that says
so; the docs show answers { jevResult(body, secondArg()) } for questions built
per call (#47).
- Every test task sets JEV4K_ENV_PROBE (SIMCTL_CHILD_-prefixed on simulators, and
passed to the Docker Linux containers), and PlatformEnvTest checks that
platformGetenv reads it on every platform (#51).
- mapModels's rejection branches (#52) and a supplied engine's timeouts (#54) are
tested; the real-CIO timeout test warms CIO up first (#56); BlockingJevTest
checks the forwarded questions (#83).
- make live-tests fails at once without TYPESAFE_API_KEY (#57).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WQjZxyKov2izXjyWbYdCc5
Step 9 of the code review (docs/code-review-2026-09-27.md): - CI's build job sets up QEMU and runs make docker-linux-tests, the only place linuxArm64's tests run; the release checklist gives it its own checkbox (#60). - check no longer processes, compiles or links test binaries the host can't run: iosX64's off an Intel Mac, mingwX64's off Windows (#62). - javac compiles with --release 17, matching kotlinc's -Xjdk-release (#63). - -XX:+EnableDynamicAgentLoading is passed on every JDK; 11 and 17 accept it, and only the warning is new in 21 (#64). - Corrected comments: coverage after a JVM test failure (#65), master runs under the concurrency group (#66), the Kover skip example (#67), and why dependabot leaves out website/uv.lock (#68). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WQjZxyKov2izXjyWbYdCc5
… reach Step 10 of the code review (docs/code-review-2026-09-27.md): - jev4k never logs, but Ktor uses SLF4J, which warns on stderr without a binding; the docs say so and suggest slf4j-nop (#72). - RetryPolicy's defaults match the JS SDK; Python shares the retries but doesn't cap hints and adds a 30 s budget per call, which jev4k lacks (#49). - The js and wasmJs artifacts are built and tested for Node.js but would load in a browser, which a browser app should avoid by calling its own backend (#74). - Java can see the @PublishedApi helpers behind choice<E>(), but they aren't supported API (#45). - .env reaches only the JVM tasks (#73); JavaInterop.java's comment lists what it pins (#82); CLAUDE.md says which SDK each type name follows (#84); two README formatting fixes (#85). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WQjZxyKov2izXjyWbYdCc5
Step 11 of the code review (docs/code-review-2026-09-27.md), which completes it: - Line search, categorize and countFruits handle empty and oversized inputs, and the Search page gains the 255-option caveat (#16). - The model router rounds an uncertain estimate up to at least LARGE instead of capping it there (#75). - The fan-out examples bound their concurrency (#76), and ranking reads stored results, so both roles are ranked from one assessment (#77). - The due-date month and the invoice total can answer none (#78). - Guardrail policy is pure code over one assessment, applied under both policies (#80), and an uncertain self-harm signal goes to a person rather than being escalated to a block, unlike TypeSafe's cookbook (#79). - The extraction checks carry explicit criteria, an empty field gets only an absence check, and an empty extraction escalates instead of throwing (#81). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WQjZxyKov2izXjyWbYdCc5
…l cases A /simplify pass over the code-review fixes (8c89c87..HEAD): - One apiError helper builds a failed response's exception for the normal and the oversized-body paths, and ResponseInfo.fail is the single way a response error is thrown, so parseObject reads as three straight guards. - One sendProblem check (nesting, NaN or infinity) covers the state, question entries and extraBody, which closes a gap: a NaN in extraBody passed build(). The per-call model reuses the config's trimming rule. - QuestionProvider.provideDelegate catches a question that fails to build, in one place instead of three builders; that also covers an enum option whose JevOption.entry fails. - LiveSmokeTest is gated on JEV4K_LIVE alone, so a live run without a key fails instead of skipping, replacing the Makefile guard. PlatformEnvTest checks PATH, replacing a probe variable wired through every test task type. - One build.gradle.kts block skips every test binary the host can't run, the device-less tvOS/watchOS simulators included; CI runs only linuxArm64 in Docker, via LINUX_TEST_TARGETS, since the build job already ran linuxX64. - Test helpers: localClient in ClientJvmTest, a testDefaults default, and a one-line request-head read in RawServer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WQjZxyKov2izXjyWbYdCc5
…escriptions - jev4k encodes its one request body itself (extraBody merged in), so the ContentNegotiation plugin, its Accept-merge setting and two runtime dependencies are gone: jev4k-jvm's POM lists five, down from seven. A new test pins the request bytes the plugin produced, and they are unchanged. The JS lockfiles lose @js-joda/core, which one of the removed modules pulled in. - CHANGELOG.md and RELEASE_NOTES.md date 0.2.0 on 2026-09-28. - CLAUDE.md no longer says the JVM ABI dump still matches 0.1.0's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WQjZxyKov2izXjyWbYdCc5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Warning
Don't merge until 0.2.0 is on Maven Central. This branch moves the install instructions to the new
com.pambrose.jev4kcoordinates. Following the release checklist (docs/release-checklist.md), 0.2.0 is publishedto Central from this branch once CI is green, and only then is the PR merged.
Summary
jev4k becomes a Kotlin Multiplatform library: the JVM plus Apple, Linux, Windows and Node.js (js and wasmJs), all from
common code. It also carries every fix from a full code review of the branch,
docs/code-review-2026-09-27.md: all85 issues are fixed, each with a note on how.
com.pambrose.jev4k. Gradle builds depend oncom.pambrose.jev4k:jev4k, and Maven builds oncom.pambrose.jev4k:jev4k-jvm. 0.1.0 stays atcom.pambrose:jev4k.JevApiException.headershas lowercased names on every platform.models()returns aModelList, aList<ModelInfo>that also carries the request id.BlockingJevmethod declaresInterruptedException, for Java.jevApiExceptiontakesheadersbeforeretryAfter.http://baseUrlon another host needsallowInsecureHttp = true.JevCallOptions,withOptions) for the timeout, retry policy, headers and extrabody fields;
JevApi.blocking()for anyJevApi; millisecond members for Java wherever a setting is aDuration.JevException, and cancellations stay cancellations.to
JevConnectionException.ktor-client-content-negotiationandktor-serialization-kotlinx-jsonare gone. The request is byte for byte the same.api/).nativematrix runs macOS, the iOS, tvOS and watchOS simulators, and Windows.testjob runs the JVM suite on JDK 17, 21 and 25.ci-okjob, required by branch protection withdocsand GitGuardian, passes only when every job did.Test plan
make testson a Mac: kotlinter, detekt, the ABI check, and the JVM, js, wasmJs, macOS, iOS-simulator andtvOS-simulator tests.
make docker-linux-tests: linuxX64 and linuxArm64.cd website/jev4k && uv run zensical build --clean --strict: no issues.make publish-local: every target's artifact, sources, javadoc, POM and module;jev4k-jvmhas Java 17bytecode, the five documented dependencies and the expected manifest.
ci-ok(build, JDK matrix, native Apple and Windows),docsand GitGuardian, all green.make live-testsandmake exampleagainst the real API, before publishing.🤖 Generated with Claude Code
https://claude.ai/code/session_01WQjZxyKov2izXjyWbYdCc5