Repository navigation
Releases: chrisjenx/serialkompat
Releases · chrisjenx/serialkompat
Release list
v0.2.0
Added
- Gradle Plugin Portal publishing (#24). The
Releaseworkflow now also publishescom.chrisjenx.serialkompatto the Plugin Portal, and the plugin declares Configuration Cache and Isolated Projects support. The first Portal version needs Gradle's manual approval; until it is live, keep resolving the plugin from Maven Central.
What's Changed
- Bump version to 0.1.1-SNAPSHOT by @github-actions[bot] in #209
- ci: pin ubuntu runners to ubuntu-24.04 by @chrisjenx in #208
- Update Gradle Wrapper from 9.8.0 to 9.8.1 by @github-actions[bot] in #212
- feat(release): publish the Gradle plugin to the Plugin Portal (#24) by @chrisjenx in #211
- docs(changelog): release notes for 0.2.0 by @chrisjenx in #213
New Contributors
- @github-actions[bot] made their first contribution in #209
Full Changelog: v0.1.0...v0.2.0
v0.1.0
Security
- Secret scanning with gitleaks. A
Secret ScanCI workflow scans the full commit history on every push and PR, with--redactso a matched secret never reaches public logs. A.pre-commit-config.yamlruns the same check locally, and.gitignorenow blocks common credential and signing-key files (*.gpg,*.pem,*.p12,*.jks,.env, …). A scan of the full existing history found no leaked secrets.
Fixed
- The extractor no longer aborts on a type whose serializer can't load (#190). A
NoClassDefFoundError,ExceptionInInitializerError, or otherErrorwhile walking one type used to fail the whole extraction. That type is now an OPAQUE coverage gap and the rest extract normally. - Value classes over a nullable value keep that nullability (#199).
value class Id(val raw: String?)is now recorded askotlin.String?(also insideList/Map), so narrowing it toStringis aPROPERTY_TYPE_CHANGEDBREAK instead of passing silently. - Shape changes around a generic type parameter are now caught (#192).
List<T>→TorMap<String,T>→Map<Int,T>on a root-only generic is aPROPERTY_TYPE_CHANGEDBREAK; it used to be skipped whenever either side contained a type parameter. A hole↔concrete flip is still not a finding. ENUM_VALUE_ADDEDcoercibility counts every reader in the baseline, not just in-scope ones (#194). A required field in an out-of-scope type (for example one another module owns) now keeps the forward verdict at BREAK instead of WARN.- The sticky PR comment no longer duplicates on busy PRs, and its icon matches the job (#189). The comment lookup now paginates past 30 comments. ❌ now means the check failed (possibly with no BREAK, such as an empty baseline),
⚠️ covers BREAKs that don't fail the build underfailOnBreaking = false, and a failed run without a report shows ❌ instead of "nothing configured". - A custom
layout.buildDirectoryis honored (#195). Setting it in the build script body used to split the extract and check tasks across two directories, so a stalebuild/serialkompat/current.snapshotcould be diffed as the current schema (a false pass). serialkompatRecordreadsproject.versionlazily (#196). Aversionset in the build script body is now seen, and an unversioned project no longer fails configuration; only runningserialkompatRecordfails without a version.serialkompatCheckworks on the root project of a single-module build (#203). The nested baseline build asked for::serialkompatExtract, which Gradle rejects.- The baseline worktree checks out the resolved commit SHA, not the ref (#188). A ref that moved between resolution and checkout could cache the wrong schema under the old SHA.
- The baseline snapshot cache is keyed by serialkompat version as well as commit SHA (#202). Upgrading serialkompat re-extracts the baseline instead of reusing one an older extractor produced.
- Multi-module builds no longer run baseline extractions concurrently (#183). Under
--parallel, each module raced the others ongit worktree addand started its own nested Gradle daemon. The check tasks now take turns through a shared build service, while extraction and compilation stay parallel. The plugin is tested under--parallel, the configuration cache, and Gradle Isolated Projects. - The extractor now runs your
@Serializableclasses on your project's own runtime (#180). Previously the plugin's kotlinx-serialization and Kotlin stdlib came first on the classpath, so a project on a newer version had its serializers loaded against older runtimes. Your runtime classpath now comes first; the plugin's jars only fill gaps. SUBTYPE_ADDEDnow credits a default deserializer on sealed bases, but only for tolerant readers (#181). Adding a subtype to a sealed base with anUnknownfallback was a false forward BREAK; it is now a WARN when the old reader hasignoreUnknownKeys. WithoutignoreUnknownKeysthe real decode throws, so it is now a BREAK (the #128 WARN was fail-open there). Both verdicts are backed by real round-trip oracles.@EncodeDefaultis now read from bytecode, so a field made optional can't fail open (#158, #176). Previously a field made optional with@EncodeDefault(NEVER)underencodeDefaults=truescored SAFE, yet old readers that still require it throwMissingFieldException. NowNEVER→ BREAK,ALWAYS→ SAFE, no annotation → followsencodeDefaults, and an unreadable mode (a hand-written serializer, or a snapshot recorded before this fix) → WARN underencodeDefaults=true, never SAFE. API:EncodeDefaultModegainsABSENT,Element.encodeDefault == nullnow means "unknown", andChange.ElementOptionalityChangedgainsnewEncodeDefault.- Unresolved
@Contextualelements are now an OPAQUE coverage gap instead of passing silently (#131). Their wire shape is chosen at runtime, so the gate can't see it; it used to report no change even when the real shape changed. ENUM_VALUE_ADDEDforward is now sound when the old reader usescoerceInputValues(#129). The old reader coerces an unknown value only into a field's default, so the verdict is WARN only when every baseline field reading that enum has a default. A required field, aList/Mapuse, or a top-level decode still throws, so those stay a BREAK.- List values containing whitespace no longer corrupt the snapshot file (#146). A
jsonNamesor enum value with a space, tab, or newline used to split apart or break the file on parse.parse(serialize(s)) == snow holds for every snapshot; whitespace-free output is byte-identical and older snapshots still parse. (Follow-up to #56.) - The gate fails closed on an empty baseline (#78). A baseline with zero contracts used to diff as "everything added → safe", hiding removals;
serialkompatChecknow fails with a clear message. Setserialkompat { failOnEmptyBaseline }tofalsefor first-time adoption withOPT_IN/OPT_OUTdiscovery, when the baseline ref has no@Serializabletypes yet. - Documentation reconciled with the shipped code (#87). The design doc's Gradle DSL and rule names now match the real
SerialkompatExtensionandRulesconstants, and a phantomserialkompatDumptask was removed. Version references were updated to Kotlin 2.4.0 and kotlinx-serialization 1.11.0. - More rules are now proven against the real library by round-trip oracles (#86). New coverage: numeric widening (
Float→Double,Byte→Int), numeric narrowing (Long→Int, BREAK both ways), and non-null→nullable. All agree with the classifier. - Baselines survive crashes (#83). The snapshot cache now writes atomically, so a crash can't leave a half-written baseline that a later run trusts. A leftover git worktree from a killed run is cleaned up automatically instead of wedging every later run.
- The CLI's
--directionaccepts the space-separated form (#85).--direction BACKWARDused to be ignored silently (falling back toFULL) and shifted the file arguments. Both--direction=VALUEand--direction VALUEnow work, and a--directionwith no value exits 2 with a usage error. - Escaping gaps closed in the snapshot file and JSON report (#84). The
classDiscriminatorandnamingStrategyconfig values now round-trip exactly, avoiding spurious or missed config diffs.JsonReporternow escapes every control character, so a control character in a@SerialNameno longer produces invalid JSON. Existing snapshots are byte-identical. - The GitHub composite action no longer interpolates untrusted inputs into scripts (#80).
${{ inputs.ref }},inputs.task,inputs.gradle-args, andinputs.report-pathwere substituted into script bodies, which allowed shell/JS injection and could bypass the gate. All inputs now pass throughenv:, and the requiredpull-requests: writepermission is documented. - One unresolvable type no longer aborts the whole extraction (#81). A single type that failed to load used to drop every type. Each type is now resolved independently, and a failure becomes an OPAQUE coverage gap (reported as a WARN) while the rest extract normally.
- A custom
JsonNamingStrategyis now recorded by its class name (#82). A strategy without its owntoString()produced a different identity hash in each JVM, which caused a phantomCONFIG_NAMING_STRATEGYBREAK on every PR. Built-in strategies are unchanged. - Removing or renaming a field under a tolerant reader is now a WARN, not a false
SAFE(#77). WithignoreUnknownKeys=truethe old payload still decodes, but the removed field's data is silently lost.PROPERTY_REMOVEDbackward is now WARN for a tolerant reader and BREAK for a strict one, so a field rename (remove + add) now surfaces as one backward WARN instead of no findings at all. - The snapshot file now round-trips names that contain its delimiters.
@SerialName,@JsonNames, and@JsonClassDiscriminatorvalues may contain spaces, newlines, tabs, or backslashes, which used to corrupt the file. These names are now escaped; names without delimiters serialize byte-for-byte as before. - The CLI never crashes on bad input. A missing or malformed snapshot file, an unknown flag, or an invalid
--directionnow exits 2 with anerror:message instead of a stack trace. Added--help/-h. Exit codes: 0 ok, 1 breaking, 2 usage/input error. @JvmInline value classes are now recorded as their underlying wire type. A field typedUserId(val raw: Int)is recorded askotlin.Int, so swapping a primitive for a wire-identical value class (or back) no longer produces a falseBREAK. A value class that wraps a@Serializabletype still checks that type.
Added
- Seven more
Jsonsettings are captured and classified (#198):useArrayPolymorphism,allowStructuredMapKeys,allowSpecialFloatingPointValues,isLenient,decodeEnumsCaseInsensitive,allowTrailingComma, andallowComments. New rulesCONFIG_ARRAY_POLYMORPHISM(BREAK b...