Skip to content

Releases: chrisjenx/serialkompat

Release list

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 14 Oct 18:04
c6571c7

Added

  • Gradle Plugin Portal publishing (#24). The Release workflow now also publishes com.chrisjenx.serialkompat to 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

Choose a tag to compare

@github-actions github-actions released this 08 Oct 17:54
0f9b6cc

Security

  • Secret scanning with gitleaks. A Secret Scan CI workflow scans the full commit history on every push and PR, with --redact so a matched secret never reaches public logs. A .pre-commit-config.yaml runs the same check locally, and .gitignore now 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 other Error while 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 as kotlin.String? (also inside List/Map), so narrowing it to String is a PROPERTY_TYPE_CHANGED BREAK instead of passing silently.
  • Shape changes around a generic type parameter are now caught (#192). List<T> → T or Map<String,T> → Map<Int,T> on a root-only generic is a PROPERTY_TYPE_CHANGED BREAK; it used to be skipped whenever either side contained a type parameter. A hole↔concrete flip is still not a finding.
  • ENUM_VALUE_ADDED coercibility 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 under failOnBreaking = false, and a failed run without a report shows ❌ instead of "nothing configured".
  • A custom layout.buildDirectory is honored (#195). Setting it in the build script body used to split the extract and check tasks across two directories, so a stale build/serialkompat/current.snapshot could be diffed as the current schema (a false pass).
  • serialkompatRecord reads project.version lazily (#196). A version set in the build script body is now seen, and an unversioned project no longer fails configuration; only running serialkompatRecord fails without a version.
  • serialkompatCheck works 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 on git worktree add and 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 @Serializable classes 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_ADDED now credits a default deserializer on sealed bases, but only for tolerant readers (#181). Adding a subtype to a sealed base with an Unknown fallback was a false forward BREAK; it is now a WARN when the old reader has ignoreUnknownKeys. Without ignoreUnknownKeys the 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.
  • @EncodeDefault is now read from bytecode, so a field made optional can't fail open (#158, #176). Previously a field made optional with @EncodeDefault(NEVER) under encodeDefaults=true scored SAFE, yet old readers that still require it throw MissingFieldException. Now NEVER → BREAK, ALWAYS → SAFE, no annotation → follows encodeDefaults, and an unreadable mode (a hand-written serializer, or a snapshot recorded before this fix) → WARN under encodeDefaults=true, never SAFE. API: EncodeDefaultMode gains ABSENT, Element.encodeDefault == null now means "unknown", and Change.ElementOptionalityChanged gains newEncodeDefault.
  • Unresolved @Contextual elements 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_ADDED forward is now sound when the old reader uses coerceInputValues (#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, a List/Map use, or a top-level decode still throws, so those stay a BREAK.
  • List values containing whitespace no longer corrupt the snapshot file (#146). A jsonNames or enum value with a space, tab, or newline used to split apart or break the file on parse. parse(serialize(s)) == s now 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; serialkompatCheck now fails with a clear message. Set serialkompat { failOnEmptyBaseline } to false for first-time adoption with OPT_IN/OPT_OUT discovery, when the baseline ref has no @Serializable types yet.
  • Documentation reconciled with the shipped code (#87). The design doc's Gradle DSL and rule names now match the real SerialkompatExtension and Rules constants, and a phantom serialkompatDump task 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 --direction accepts the space-separated form (#85). --direction BACKWARD used to be ignored silently (falling back to FULL) and shifted the file arguments. Both --direction=VALUE and --direction VALUE now work, and a --direction with no value exits 2 with a usage error.
  • Escaping gaps closed in the snapshot file and JSON report (#84). The classDiscriminator and namingStrategy config values now round-trip exactly, avoiding spurious or missed config diffs. JsonReporter now escapes every control character, so a control character in a @SerialName no 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, and inputs.report-path were substituted into script bodies, which allowed shell/JS injection and could bypass the gate. All inputs now pass through env:, and the required pull-requests: write permission 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 JsonNamingStrategy is now recorded by its class name (#82). A strategy without its own toString() produced a different identity hash in each JVM, which caused a phantom CONFIG_NAMING_STRATEGY BREAK 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). With ignoreUnknownKeys=true the old payload still decodes, but the removed field's data is silently lost. PROPERTY_REMOVED backward 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 @JsonClassDiscriminator values 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 --direction now exits 2 with an error: 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 typed UserId(val raw: Int) is recorded as kotlin.Int, so swapping a primitive for a wire-identical value class (or back) no longer produces a false BREAK. A value class that wraps a @Serializable type still checks that type.

Added

  • Seven more Json settings are captured and classified (#198): useArrayPolymorphism, allowStructuredMapKeys, allowSpecialFloatingPointValues, isLenient, decodeEnumsCaseInsensitive, allowTrailingComma, and allowComments. New rules CONFIG_ARRAY_POLYMORPHISM (BREAK b...
Read more