Skip to content

fix(cdc): preserve valid value when Debezium key JSON is malformed - #209

Closed
seonghobae wants to merge 17 commits into
developfrom
fix/debezium-mapper-malformed-key-fallback-622e5e6
Closed

fix(cdc): preserve valid value when Debezium key JSON is malformed#209
seonghobae wants to merge 17 commits into
developfrom
fix/debezium-mapper-malformed-key-fallback-622e5e6

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Purpose

Fix a path-disjoint CDC data-integrity defect from exact protected develop@622e5e6c3d534f230c390f10e3832efadfc01825. DebeziumChangeRecordMapper historically parsed the required value envelope and optional key inside one shared try block. A malformed optional key therefore threw from extractPk(...), was caught by the outer value-parser catch, and caused the mapper to return Optional.empty() even when the value envelope was valid and contained a usable after.id/before.id fallback.

This differs from the live replica applier, which already treats malformed key JSON as unavailable key metadata and falls back to the value payload rather than discarding the event. The defect could silently turn key-side corruption into whole-event loss for consumers of the canonical mapper.

This Draft is independent of #201's registry-ID work and #202's target-capability/execution-authority decision. It changes only DebeziumChangeRecordMapper and its focused test; no registry, target connector, Kafka publication, database schema, or canonical documentation branch is touched.

Exact current identity

  • protected/live base: develop@622e5e6c3d534f230c390f10e3832efadfc01825;
  • branch: fix/debezium-mapper-malformed-key-fallback-622e5e6;
  • fail-first source head: f9d38a598b1ad3490777a75bcee10af64c3da390;
  • exact current GREEN source head: 40e47f69bb76de7e68073e0d1136f1b94db31f3d;
  • current synthetic merge preview: de61db0c611f11738cc3ca9e048be3cec0385907;
  • comparison to exact protected base: two commits ahead, zero behind, merge base exactly protected develop;
  • changed paths: exactly the mapper and its focused test;
  • Draft / mergeable: true / true;
  • formal reviews: none;
  • unresolved review threads: zero;
  • CodeRabbit exact-source commit status: success, but status-only evidence is not formal approval.

Every check/review from a predecessor source head is historical after source movement.

RCA

  • Immediate cause: optional key parsing propagated IOException into the same catch that rejects malformed required value JSON.
  • Technical root cause: required event-envelope validity and optional key-metadata validity shared one failure boundary.
  • Control failure: existing mapper tests covered absent keys and value-derived delete IDs but did not exercise a corrupt optional key with an otherwise valid value.
  • Product impact: a value event that can still be identified through its after.id/before.id fallback could be silently discarded.

TDD evidence

RED — exact source f9d38a598b1ad3490777a75bcee10af64c3da390

The fail-first commit changed only DebeziumChangeRecordMapperTest. malformedKeyFallsBackToValidAfterIdentifierWithoutDroppingTheEvent supplies a valid update envelope (after.id=42) and deliberately malformed optional key JSON.

CI run 31353531290, macOS job 93348726197, checked out GitHub synthetic merge 0179ea29694f5c4910da2d96cab157f5ce7dddd7 (Merge f9d38a... into 622e5e6...). Production and test compilation completed successfully. ETL ran 275 tests with zero failures/errors/skips. CDC ran 107 tests with exactly one failure, zero errors/skips: the new mapper assertion expected a present event but production returned empty. Existing mapper/CDC tests were green before the reactor stopped. The first failing boundary was therefore the intended production mapper behavior, not setup/import/fixture/dependency failure.

GREEN — exact current source 40e47f69bb76de7e68073e0d1136f1b94db31f3d

The production correction keeps required value parsing fail-closed but isolates optional key parsing:

  • null/blank optional keys still produce no key metadata;
  • malformed key JSON now produces an empty key map instead of aborting the whole event;
  • the existing deterministic after.id, then before.id, primary-key fallback can execute;
  • malformed/absent required value JSON still returns Optional.empty();
  • no raw key/value material is logged and no additional payload shape is accepted;
  • public map(...) Javadoc states the required-value versus optional-key failure boundary and return semantics.

All exposed workflows associated with this exact current source head are terminal-success:

  • CI 31353700303: success on macOS, Ubuntu and Windows;
  • Dependency Review 31353700282: success;
  • CycloneDX SBOM 31353700279: success;
  • SAST Semgrep 31353700288: success;
  • Security Scan 31353700289: success.

macOS CI job 93349216528 proves the exact repaired behavior on GitHub's merge preview: it checked out synthetic merge de61db0c611f11738cc3ca9e048be3cec0385907 (Merge 40e47f69... into 622e5e6...), the full Maven reactor completed BUILD SUCCESS, CDC ran 107 tests with zero failures/errors/skips, and DebeziumChangeRecordMapperTest passed 4/4 including the new malformed-key fallback case.

This is strong merged-tree causal proof but not literal-source acceptance evidence. The same CI log independently reproduces the inherited protected JaCoCo false green: report and check both say Analyzed bundle 'etl-service' with 0 classes followed by success. Accordingly, source identity, scanner completeness (#196), selected-coverage non-vacuity (#162/#164), repository-wide coverage scope (#205), and formal independent review remain separate authorities.

Shared acceptance boundary

Keep Draft. The mapper defect itself is GREEN, but protected merge is not authorized until accepted literal-source execution/security evidence, complete vulnerability-resolution evidence, non-vacuous applicable owned-production coverage, zero valid unresolved findings, qualifying independent non-author formal approval where required, and branch protection all hold on the unchanged current identity. No synthetic-merge, other-PR, predecessor-head, status-only, queued, skipped, incomplete-scanner or failed evidence transfers.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7d576159-0533-4e23-9f1e-3958e17acaa3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

seonghobae and others added 15 commits August 11, 2026 17:19
* test(cdc): reject duplicate configured source ids

* fix(cdc): reject duplicate configured source ids
* test(etl): prove accepted status URL invariant

* fix(etl): enforce accepted status URL invariant
* test(etl): prove durable job failure metadata invariant

* test(etl): preserve valid failed job representation

* fix(etl): validate snapshot failure metadata lifecycle

* fix(etl): validate response failure metadata lifecycle
* test(etl): require recursive ChangeRecord snapshots

* test(etl): make recursive snapshot RED deterministic

* fix(etl): recursively snapshot ChangeRecord containers

* test(etl): reject cyclic ChangeRecord containers

* fix(etl): reject cyclic ChangeRecord containers
* test(cdc): reproduce mutable canonical record maps on live develop

* fix(cdc): snapshot canonical change record maps
* test(security): reproduce local env tracking gap on live develop

* fix(security): ignore developer-local environment files

* test(security): reproduce Docker env build-context gap

* fix(security): exclude local env files from Docker context
* test(cdc): reject negative Kafka retry settings

* fix(cdc): reject negative Kafka retry settings
* test(config): reproduce complete connector alias contract

* fix(config): mirror complete connector setting surface
* test(coverage): reject empty JaCoCo production selection

* fix(coverage): make JaCoCo production selection non-vacuous

* test(coverage): align durable-job policy with non-vacuous bundle gate

* test(coverage): cover durable-job defensive branches

* test(coverage): close current durable-job model branches
* test(security): replay Jackson LTS baseline on live develop

* fix(security): align Jackson with patched 2.21 LTS BOM

* test(security): reject Jackson BOM decoy evidence

* test(security): validate Jackson BOM structurally
* test(cdc): replay secure DDL default RED on live develop

* fix(cdc): secure DDL validation default
* test(build): replay resource encoding RED on live develop

* fix(build): pin Maven resource encoding
* test(supply-chain): require immutable Docker base images

* fix(supply-chain): pin Docker base image digests

* test(supply-chain): require Docker digest doctoring

* docs(supply-chain): document Docker digest pinning

* docs(supply-chain): record Docker digest pinning

---------

Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>

Copy link
Copy Markdown
Collaborator Author

Superseded by current-base replacement #304 after fresh preservation proof. At #209b0d092b, the unique malformed-key mapper work is exactly DebeziumChangeRecordMapper.java blob df10e7084243b8a7b9c81a8d14575b0523fc37fb plus DebeziumChangeRecordMapperTest.java blob ffec5d11e13e59ba68f7619984115607dc544f68. #3042af6c9a now carries those same two blobs byte-for-byte on live develop@502d1e2136f18b7dea3baa792559128dc87fec39, while this stale branch has accumulated 35 changed files unrelated to the bounded defect. Old checks/reviews do not transfer; #304 must earn fresh exact-head evidence independently. Closing this duplicate preserves this branch/history as evidence without merging or rewriting it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant