Skip to content

[reference] R2DBC net-new instrumentation (io.r2dbc:r2dbc-spi 1.0.0, toolkit output) - #12032

Draft
jordan-wong wants to merge 4 commits into
masterfrom
eval/r2dbc-net-new-20260721
Draft

[reference] R2DBC net-new instrumentation (io.r2dbc:r2dbc-spi 1.0.0, toolkit output)#12032
jordan-wong wants to merge 4 commits into
masterfrom
eval/r2dbc-net-new-20260721

Conversation

@jordan-wong

@jordan-wong jordan-wong commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

What

Reference PR from the apm-instrumentation-toolkit's net-new generation workflow, targeting io.r2dbc:r2dbc-spi:1.0.0.RELEASE. No dd-trace-java module exists for R2DBC today, so this was not a blind regen — there was nothing to hide/restore. This measures from-scratch generation quality against an SPI-only Maven coordinate (no concrete driver dependency).

Generated module: dd-java-agent/instrumentation/r2dbc/r2dbc-1.0/

Toolkit reviewer verdict: needs_work

The toolkit's own review_cycle ran 5 iterations and did not reach approved (todos_remaining=15 after iter5, the apparent iteration cap). Total cost: $65.81, duration: ~2h17m.

What went right (structural checks all clean):

  • Correct SPI-first targeting throughout (io.r2dbc.spi.Statement/Connection/Batch), no concrete-driver coupling
  • Correct single-name super("r2dbc"), no spurious version alias
  • Correctly classified as Cat A (span-creating); all Cat B checks correctly "not applicable"
  • No scheduler-exclusion, overload-selection, or context-store-hoisting issues

What's broken — real semantic-completeness gaps (dominant finding):

  • db.name (REQUIRED tag) is never set. ConnectionInstrumentation only extracts from ConnectionMetadata, which doesn't expose database/host/port/user. Root cause (per reviewer): should hook ConnectionFactory.create() or the Connection constructor instead, which exposes ConnectionFactoryOptions with that data.
  • peer.hostname, db.user, network.destination.port (RECOMMENDED tags) — all missing for the same reason.
  • Dead code: R2dbcConnectionInfo has fields (dbInstance, dbUser, dbHostname) defined but never populated.
  • BatchInstrumentation spans have zero connection metadata tags.
  • Test coverage gap mirrors the code gap; inconsistent db.type value between batch ("r2dbc") and statement ("testdb") tests.

Minor: 1 inline narrative comment to remove; missing explicit muzzle fail blocks for 0.8.x/0.9.x version families. Originally noted as "not urgent — assertInverse=true already isolates," but see CI Triage Status below: that assertInverse=true was actually asserting a false compatibility boundary and has been removed as part of a CI fix, so there is now no automated check that pre-1.0 releases are excluded — this remains a real (if low-priority) gap.

This PR is left as-is (not iterated further) so the diff reflects exactly what the toolkit produced — do not fix findings on this branch; it's a research artifact, not a merge candidate.

Comparison with OpenTelemetry's R2DBC instrumentation (reference for the correct hook point)

OTel Java solved exactly the gap above by hooking a different SPI entry point. R2DBC exposes connection identity (host/port/db/user) only in io.r2dbc.spi.ConnectionFactoryOptions at connection-factory creation — not on Connection/ConnectionMetadata (which exposes only getDatabaseProductName()/getDatabaseVersion()). Where you hook decides whether you can populate these tags at all.

this PR (dd-trace-java) OpenTelemetry Java
Hook point Connection.createStatement() + connection.getMetadata() (ConnectionInstrumentation.java:30,38,53) ConnectionFactories.find(ConnectionFactoryOptions) (instrumentation/r2dbc-1.0/javaagent/.../R2dbcInstrumentation.java)
Carrier R2dbcConnectionInfo DbExecution holding captured ConnectionFactoryOptions (.../library/.../internal/DbExecution.java)
db.name/db.namespace ❌ can't — ConnectionMetadata lacks it ConnectionFactoryOptions.DATABASE
peer.hostname/server.address ConnectionFactoryOptions.HOST
port ConnectionFactoryOptions.PORT
db.user ConnectionFactoryOptions.USER
DBM traceparent (full mode) ❌ passes traceParent=null (ConnectionInstrumentation.java:64) ✅ propagator-driven sqlcommenter injects real W3C traceparent

OTel history check: no PR/issue in opentelemetry-java-instrumentation's r2dbc-1.0 shows these attributes were ever missing-then-added — the factory-level hook is original design, chosen precisely because it's the one place the full connection params are structured data. So this isn't a shared gap; it's a design divergence.

Takeaway: the toolkit correctly recognized R2DBC as SPI-shaped and hooked io.r2dbc.spi.* interfaces — but hooked the wrong SPI entry point for connection identity. The remediation is to capture ConnectionFactoryOptions at factory-create time and thread it forward (as OTel's DbExecution does), instead of reading ConnectionMetadata per statement. Full write-up: toolkit repo docs/eval-research/r2dbc-otel-comparison.md.

CI Triage Status

Last updated: 2026-07-22

Confirmed research findings (do not fix — core instrumentation-logic gap):

  • [Reviewer-surfaced, not CI-surfaced] db.name (REQUIRED tag), peer.hostname/db.user/network.destination.port (RECOMMENDED tags) never populated. ConnectionInstrumentation only extracts from ConnectionMetadata (product name/version only) instead of hooking ConnectionFactory.create()/the Connection constructor, which exposes ConnectionFactoryOptions with the actual host/port/db/user data. No CI check catches this — there's no assertion anywhere that these tags must be present — so this finding would otherwise only live in prose. See "What's broken" above for full detail; candidate skill fix is N-DBM-2 (silent-narrowing process gap) in the toolkit's docs/eval-research/BACKLOG.md.
  • [Reviewer-surfaced, not CI-surfaced] BatchInstrumentation spans carry zero connection-metadata tags — same root cause as above, compounded (batch path never wires through what statement path partially has).
  • [Reviewer-surfaced, not CI-surfaced] Dead code: R2dbcConnectionInfo fields (dbInstance, dbUser, dbHostname) defined but never populated — same root cause.

Fixed (scaffolding, root cause is a documented finding):

  • dd-gitlab/muzzle: [4/8]muzzle-AssertFail-io.r2dbc-r2dbc-spi-0.8.6.RELEASE and -0.9.1.RELEASE FAILED: "Instrumentation unexpectedly passed Muzzle validation". The generated build.gradle declared versions = "[1.0.0.RELEASE,)" with assertInverse = true, asserting pre-1.0 releases must fail — but io.r2dbc.spi.Statement/Connection/Batch are structurally unchanged across 0.8.x/0.9.x/1.0.x, so they pass anyway. Fixed in commit 521b0d52a1: dropped assertInverse = true. Verified locally AND confirmed green in CI (all muzzle: [*/8] shards SUCCESS post-push). Does not touch the hook point, tags, or integration name — the dominant finding above (missing db.name/peer.hostname/db.user/etc.) is unrelated and unaddressed here on purpose.

Fixed (config/mechanical):

  • dd-gitlab/buildverifyAgentJarIntegrations failed: "Integration list differs from metadata/agent-jar-checks.properties" — super("r2dbc") registered but the golden file was never updated. Fixed in commit 368d2094ce: added r2dbc,\ alphabetically between quartz and ratpack. Verified locally AND confirmed green in CI post-push.

Classified as flake / unrelated:

  • (none yet)

Still unclassified: none.

Process note (not a code finding):

  • dd-gitlab/validate_supported_configurations_v2_local_file — Same pattern as PR [reference] eval: blind regeneration of PostgreSQL JDBC driver (toolkit output) #11997's PostgreSQL finding, investigated the same way: manually checked https://feature-parity.us1.prod.dog/#/configurations for DD_TRACE_R2DBC_ENABLED/DD_TRACE_R2DBC_ANALYTICS_ENABLED/DD_TRACE_R2DBC_ANALYTICS_SAMPLE_RATE — none visible in the registry UI, consistent with these being genuinely new config names with no existing registry entry. Our local entries follow the same convention as master's real integrations. Confirmed process note — remediation requires feature-parity registry write access neither of us has, not a dd-trace-java commit.

🤖 Generated by apm-instrumentation-toolkit. Not a merge candidate — reference/research artifact only.

Reference PR from toolkit net-new generation (no prior dd-trace-java
module exists for R2DBC, so this is not a blind regen). Generated
against io.r2dbc:r2dbc-spi:1.0.0.RELEASE.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@jordan-wong jordan-wong added tag: ai generated Largely based on code generated by an AI or LLM tag: apm integration toolkit Changes generated by DataDog/apm-instrumentation-toolkit as part of IDM AIT experimentation labels Jul 22, 2026
jordan-wong and others added 2 commits July 22, 2026 17:06
The advice hooks io.r2dbc.spi.Statement/Connection/Batch interfaces,
which are structurally unchanged across the 0.8.x/0.9.x/1.0.x
releases -- assertInverse was asserting that all versions outside
[1.0.0.RELEASE,) must fail muzzle, which is false for the older-but-
compatible SPI releases and caused
muzzle-AssertFail-io.r2dbc-r2dbc-spi-{0.8.6,0.9.1}.RELEASE to fail
CI ("unexpectedly passed Muzzle validation").

This does not touch the hook point -- R2DBC's core finding (missing
db.name/peer.hostname/db.user/network.destination.port from hooking
ConnectionMetadata instead of ConnectionFactoryOptions, see
docs/eval-research/hypotheses/r2dbc.md on the toolkit repo) is
unrelated and unaddressed here on purpose.

Verified locally: `./gradlew :dd-java-agent:instrumentation:r2dbc:r2dbc-1.0:muzzle`
BUILD SUCCESSFUL.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Integration list differs from metadata/agent-jar-checks.properties --
super("r2dbc") registered in R2dbcInstrumenterModule.java but the
golden file was never updated. Inserted alphabetically between
quartz and ratpack.

Hand-edited rather than running the Gradle golden-file-update task
directly, but verified via `./gradlew :dd-java-agent:verifyAgentJarIntegrations`
BUILD SUCCESSFUL.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@datadog-prod-us1-3

datadog-prod-us1-3 Bot commented Jul 22, 2026

Copy link
Copy Markdown

🎯 Code Coverage (details)
Patch Coverage: 100.00%
Overall Coverage: 57.24% (-0.15%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: ada5972 | Docs | Datadog PR Page | Give us feedback!

@dd-octo-sts

dd-octo-sts Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

🟢 Java Benchmark SLOs — All performance SLOs passed

Suite Status
Startup 🟢 pass

SLO thresholds are defined here based on automatically generated metrics. A warning is raised when results are within 5% of the threshold.

PR vs. master results
Scenario Candidate master Δ (95% CI of mean)
startup:insecure-bank:iast:Agent 13.99 s 14.07 s [-1.5%; +0.4%] (no difference)
startup:insecure-bank:tracing:Agent 12.96 s 13.11 s [-1.9%; -0.4%] (maybe better)
startup:petclinic:appsec:Agent 17.02 s 16.65 s [+1.0%; +3.4%] (significantly worse)
startup:petclinic:iast:Agent 16.92 s 16.38 s [-0.9%; +7.6%] (no difference)
startup:petclinic:profiling:Agent 16.86 s 16.92 s [-1.7%; +1.0%] (no difference)
startup:petclinic:sca:Agent 17.02 s 16.73 s [+0.8%; +2.8%] (maybe worse)
startup:petclinic:tracing:Agent 16.14 s 16.10 s [-0.7%; +1.3%] (no difference)

Commit: 368d2094 · CI Pipeline · Benchmarking Platform UI


Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion.

@jordan-wong jordan-wong changed the title [reference] R2DBC net-new instrumentation (io.r2dbc:r2dbc-spi 1.0.0) R2DBC net-new instrumentation (io.r2dbc:r2dbc-spi 1.0.0) Jul 23, 2026
@ygree

ygree commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 368d2094ce

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// Connection may be closed or metadata unavailable
}
if (INJECT_COMMENT) {
sql = R2dbcSQLCommenter.inject(sql, null, dbType, null, null, null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pass an actual traceparent for DBM full mode

When DD_DBM_PROPAGATION_MODE=full, this is the only point where the SQL text is modified, but the traceParent argument is always null because the R2DBC span is not created until execute(); the later execute advice only sets _dd.dbm_trace_injected and cannot update the SQL. The resulting DBM comment has no traceparent, so full-mode DBM cannot correlate the database query with the APM span despite the span being tagged as injected; delay comment injection until a span exists or avoid setting the injected tag unless a traceparent was included.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No code change — recorded as a documented finding, not fixed on this branch. This is a [reference] not-for-merge research PR, and this is its central instrumentation-logic finding (the missing traceparent + broader connection-metadata gap): it can't be fixed without moving comment injection out of createStatement() to after the span exists, which is the same span-lifecycle restructure as the defer-until-subscribe finding. OpenTelemetry solves it by hooking ConnectionFactories.find() and injecting via a propagator-driven commenter — side-by-side comparison with line refs: https://github.com/DataDog/apm-instrumentation-toolkit/blob/eval/java/docs/eval-research/r2dbc-otel-comparison.md . Captured in the PR body and docs/eval-research/hypotheses/r2dbc.md.

Comment on lines +61 to +64
public void onComplete() {
try {
DECORATE.beforeFinish(span);
span.finish();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep spans open through R2DBC result consumption

For R2DBC callers that consume the returned Result later (for example Mono.from(statement.execute()).flatMap(result -> result.map(...)) or getRowsUpdated()), the outer Publisher<Result> can complete before the row/update-count Publisher is consumed, but this line finishes the database span at that outer completion. Errors and latency that occur while consuming rows or update counts are then missed; wrap the emitted Result consumption Publishers or otherwise defer finishing until the result stream terminates.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No code change — documented finding, not fixed. Correct: the span finishes when the outer Publisher<Result> completes, so errors/latency during row/update-count consumption (e.g. flatMap(result -> result.map(...)), getRowsUpdated()) are missed. Fixing means wrapping the emitted Result's downstream consumption publishers — coupled to the same span-lifecycle restructure as the defer-until-subscribe finding above. Recorded as an instrumentation-logic finding in the PR body + hypotheses/r2dbc.md; left as-generated on this reference PR.


String sql = info != null ? info.getSql() : null;

AgentSpan span = startSpan("r2dbc", R2DBC_QUERY);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Defer R2DBC spans until subscription

When clients assemble a reactive pipeline without immediately subscribing (common with Spring/Reactor, or if a branch never subscribes), this starts a span during Statement.execute() even though no database work has begun. That span is attached to the assembly-time context and can stay open forever if the publisher is never subscribed, or record inflated latency if subscribed later; create/decorate the span in the returned publisher's subscribe path instead.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No code change — documented finding, not fixed. Correct diagnosis: the span is started in execute()'s OnMethodEnter rather than in the returned publisher's subscribe(), so an assembled-but-never-subscribed pipeline leaks the span or records inflated latency. Fixing this = moving span creation/decoration into TracingPublisher.subscribe(), a fundamental restructure of the module's approach; on a reference PR that would destroy the artifact this experiment exists to show. Recorded as an instrumentation-logic finding in the PR body + docs/eval-research/hypotheses/r2dbc.md rather than patched.

Codex review (PR #12032): when a returned Publisher is cancelled
before onComplete/onError (take(1), timeout, disconnected request),
TracingSubscriber passed the driver's Subscription straight through,
so cancellation was never observed and the span created in execute()
could stay open indefinitely.

Wrap the Subscription (named TracingSubscription helper) so cancel()
finishes the span, guarded by an AtomicBoolean shared with
onComplete/onError so a cancel racing a terminal signal can't
double-finish. Registered the new helper class in
R2dbcInstrumenterModule.helperClassNames() (muzzle requires it).

Verified: compileJava + muzzle + module test suite all green.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@jordan-wong jordan-wong added the tag: do not merge Do not merge changes label Jul 30, 2026
@jordan-wong jordan-wong changed the title R2DBC net-new instrumentation (io.r2dbc:r2dbc-spi 1.0.0) [reference] R2DBC net-new instrumentation (io.r2dbc:r2dbc-spi 1.0.0, toolkit output) Jul 30, 2026
final String originalSql = sql;
String dbType = null;
try {
ConnectionMetadata metadata = connection.getMetadata();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hook-point difference vs OpenTelemetry (primary). This reads connection identity from Connection.getMetadata() (io.r2dbc.spi.ConnectionMetadata), which by SPI contract exposes only getDatabaseProductName()/getDatabaseVersion() — no host/port/database/user. OTel Java instead hooks io.r2dbc.spi.ConnectionFactories.find(ConnectionFactoryOptions) (R2dbcInstrumentation.java#L25-L34) and captures ConnectionFactoryOptions at factory-creation, then threads it forward per query in DbExecution (DbExecution.java#L84-L97) — which is why OTel can populate db.namespace/server.address/server.port/db.user and this cannot. For a shippable integration, the hook point likely needs to move to the connection factory. Full side-by-side: docs/eval-research/r2dbc-otel-comparison.md (toolkit repo).

// Connection may be closed or metadata unavailable
}
if (INJECT_COMMENT) {
sql = R2dbcSQLCommenter.inject(sql, null, dbType, null, null, null);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DBM injection difference vs OpenTelemetry. Four of six args to R2dbcSQLCommenter.inject(...) are null here — dbService, hostname, dbName, and crucially traceParent (last arg). So even in full DBM mode the SQL comment carries no traceparent, and DB-side query samples cannot be correlated to the APM trace. Root cause is the same as the hook-point comment above: this runs at createStatement() time, before the span exists, so there is no traceparent to pass. OTel avoids this by injecting through a propagator-driven commenter after the span is established (R2dbcSqlCommenterUtil). A shippable version needs the traceparent wired in (which depends on the hook-point/lifecycle change above).

if (INJECT_COMMENT) {
sql = R2dbcSQLCommenter.inject(sql, null, dbType, null, null, null);
}
return R2dbcConnectionInfo.of(originalSql, dbType, null, null, null);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dead connection-metadata fields (consequence of the hook point). R2dbcConnectionInfo.of(originalSql, dbType, null, null, null) passes null for dbInstance/dbUser/dbHostname — so those fields exist on R2dbcConnectionInfo but are never populated, and the decorator overrides that would consume them (R2dbcDecorator.dbUser/dbInstance/dbHostname, lines 58-69) all return null. Net effect on spans: db.name (REQUIRED), peer.hostname, db.user, network.destination.port are never set. OTel populates all of these from ConnectionFactoryOptions (R2dbcSqlAttributesGetter.java#L50-L116). These three comments are one finding with one fix: move the hook to the connection factory.

@Advice.OnMethodEnter(suppress = Throwable.class)
public static AgentScope onEnter() {
AgentSpan span = startSpan("r2dbc", R2DBC_BATCH);
DECORATE.afterStart(span);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Batch spans carry zero connection metadata (secondary). This advice does startSpan + afterStart but never calls onConnection/attaches any R2dbcConnectionInfo, so Batch.execute() spans have no db.*/peer.* tags at all — even less than the statement path. OTel treats batch executions through the same per-execution DbExecution carrying ConnectionFactoryOptions, so batch spans get the same connection attributes as statement spans. Same root fix (connection-factory hook) resolves this too. Lower priority than the statement path, but noting it for completeness of the shippability assessment.

gh-worker-dd-mergequeue-cf854d Bot pushed a commit that referenced this pull request Aug 25, 2026
skill(apm-integrations): sharpen SPI + muzzle rules for database category, add eager-connect idiom

From the database eval cycle (reference PRs #11996/#11997/#12032). Most
database findings turned out to be adherence gaps against rules that
already exist, not missing rules — so this sharpens the existing rules
with the concrete failure modes, plus adds one genuinely-new idiom.

instrumenter-module.md:
- SPI rule: the ForTypeHierarchy exception now explicitly covers being
  handed a CONCRETE driver that implements a JDK SPI (e.g.
  org.postgresql.jdbc.PgStatement implements java.sql.Statement), not
  just interface-only spec jars. The old wording only triggered on
  "interface-only jar", so an agent given a single concrete driver
  didn't apply it — the PostgreSQL regen (R-DB-2) fell into exactly
  this trap and shipped a concrete-class module that also collides at
  runtime with the existing jdbc/ SPI module.
- New: database clients must populate connection metadata eagerly at
  connect/factory time (JDBC DriverInstrumentation; R2DBC
  ConnectionFactoryOptions), not lazily per query (R-DB-3).

muzzle.md:
- assertInverse rule reinforced with the concrete-driver failure mode
  (R-DB-4): a pinned dependency version is not an API-shape boundary.

Draft — will be refined as feedback comes in from the database
reference PR reviews.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

skill(apm-integrations): couple module placement to a taken super() name (R-DB-1)

Database category gap sweep (2026-08-05) confirmed R-DB-1 is category-wide,
not a Cassandra quirk: any library with a version-sibling family directory
whose name differs from the integration slug will collide. The existing
"modify in place, don't create a parallel module" rule (line 47) doesn't
cover the case the Cassandra regen actually hit:

- eval slug `cassandra` != family dir `datastax-cassandra/`
- surviving siblings (datastax-cassandra-3.0/-3.8) already declare
  super("cassandra")
- under the blind protocol the same-version (4.0) module was DELETED, so
  "modify it in place" had no target — but the name was still taken

The agent created a new top-level instrumentation/cassandra/ module with a
duplicate super("cassandra") registration -> silent tracing outage (advice
never applied, zero spans, tests timed out, no build error).

Fix: grep the tree for the intended super() name BEFORE creating a module;
if any module (including untouched version-siblings) holds it, join that
family directory rather than minting a new top-level slug. Placement and
name are one decision: a taken name dictates the directory. If there is no
collision-free home, STOP and surface it.

Verified against #12114's existing commit (37e661c): the SPI-collision
case (R-DB-2) and eager-connect (R-DB-3) are already covered there; this is
the distinct version-sibling-family placement case they don't address.
Other sweep findings routed elsewhere (N-DBM-2 silent-downscope -> toolkit
prompt; reviewer-check candidates -> toolkit repo), not this skill PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

skill(apm-integrations): cross-ref DB-client design rules to category guide

Points instrumenter-module.md at the toolkit categories/database.md decision
block (force-read at target-selection) for db.instance sourcing, wrapper
pattern, DBM gating, and eager connect metadata (R-DB-3).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

skill(apm-integrations): fix dangling toolkit cross-ref + retain lazy DBInfo fallback (review #12114)

Addresses @dougqh's review: the DB-client design-rules bullet pointed at the toolkit file
`categories/database.md` "force-read at target-selection" — but that file is not in this repo
and "target-selection" is not a named SKILL.md step, so an agent reading the skill hit a dangling
pointer. Reworded to:
- name the real steps where the rules apply (Step 3 target selection / Step 5 module write);
- state plainly that these are the human-readable rules, additionally enforced as force-read
  toolkit prompt blocks (apm-instrumentation-toolkit#580) when driven by the toolkit — and that
  the toolkit file is not part of this repo;
- fold in the Codex/master-accurate note to retain a lazy parseDBInfo fallback for connect paths
  the eager Driver.connect hook doesn't cover (DataSource/proxy), not eager-only.

No rule content removed; wording only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

skill(apm-integrations): fold 3 bot-review refinements — family-name evidence, concrete-hook scope, R2DBC create() (review #12114)

Addresses valid Codex/Datadog-Autotest findings on #12114 (all confirmed against repo facts):

- R-DB-1: "taken super() name dictates the directory" was too absolute. Reworded to treat name
  matches as EVIDENCE, then confirm by coordinates/packages/muzzle. Shared config names (jax-rs
  across rs/jersey/resteasy; ci-visibility across nine) do NOT identify one family → place by
  target library; if ambiguous, STOP. Also corrected the mechanism note: equal super() names do
  not themselves cause a registration outage (InstrumenterIndex indexes by module class); the real
  harm is a duplicate same-version module (the Cassandra case, unchanged).
- R-DB-2: scoped the concrete-driver prohibition to BEHAVIORALLY-REDUNDANT advice. Vendor-only
  lifecycle/compat hooks not declared on the SPI legitimately need a concrete module
  (DBMCompatibleConnectionInstrumentation, DB2 JDBC, Tomcat Request.recycle()). Reject a concrete
  hook only when its method is already advised via the SPI.
- R2DBC: corrected the factual claim that ConnectionFactoryOptions is available at
  ConnectionFactory.create(). create() is zero-arg returning a Publisher; options must be captured
  earlier at ConnectionFactories.get(options) into a ContextStore<ConnectionFactory,Options> and
  threaded onto the async-emitted Connection.

Wording/accuracy only; no gating rule reversed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

skill(apm-integrations): R-DB-5 must keep the connection/session default, override only when richer (review #12114)

Addresses Datadog Autotest :23 (valid): "derive db.instance from the operation not the connection
default" read as "drop the default", which would lose db.instance/keyspace for operations with no
result metadata (e.g. a Cassandra write). Reworded to the correct two-phase framing — keep the
connection/session value as the DEFAULT, override per-operation only when the operation supplies a
more specific value (fully-qualified other_ks.table, or keyspace from response ColumnDefinitions).
This matches the toolkit R-DB-5 rule (session default + onResponse override), which was already
correct; only the skill one-liner was imprecise.

The other three Autotest :12/:59/:121 findings this run are re-scans already fixed by the prior
two commits (concrete-hook scoping, name-as-evidence, R2DBC create() zero-arg).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

skill(apm-integrations): broaden super() name search + fix Cassandra outage causal claim (review #12114)

Two valid Datadog Autotest catches (15:07 run):
- :52 — the name-search grep matched only the FIRST super(...) arg, missing later args
  (super("vertx","vertx-sql-client")) and names held in constants. Broadened to grep all super(...)
  args and to also scan module classes / name constants when a literal grep misses.
- :59 — the Cassandra "Concrete failure" paragraph attributed the tracing outage to two same-name
  @autoservice registrations, but modules are indexed by class name (equal names are legal). Reframed:
  the mechanism is a DUPLICATE module advising the same types (mutual suppression via the shared
  call-depth guard); the shared super("cassandra") name is a symptom, not the cause. Now consistent
  with the note added earlier.

The third finding this run (:23 db.instance keep-the-default) is a stale re-scan already fixed in
04bfe52.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

skill(apm-integrations): make DB rules standalone — remove toolkit references (review #12114)

Per architectural rule: the skill must be usable standalone; dependency direction is one-way
(toolkit references the skill, never the reverse). The prior wording pointed the reader at the
apm-instrumentation-toolkit (categories/database.md, PR #580, "when driven by the toolkit …
force-read prompt blocks"). Removed all of it — the database-client design rules now stand on
their own with no toolkit dependency. Also dropped the (R-DB-3) eval-bookkeeping tag, which is
meaningless to a standalone skill reader. Rule content unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Merge branch 'master' into skill/database-category-rules-20260730

skill(apm-integrations): muzzle result is not proof a matcher target exists (review #12114)

Addresses Codex P2 on muzzle.md: the prior text implied a muzzle pass verified the concrete
matcher target (PgStatement) exists on old versions. Muzzle derives references from advice
bytecode + explicit additional references, NOT from instrumentedType()/named(...) matcher strings,
so a matcher-only concrete class is a blind spot. Reworded to: do not infer matcher-target
presence/absence from a muzzle result; back version-dependent matcher rules with an explicit muzzle
reference or a runtime/latest-dep test. Also folded in the PostgreSQL jdbc2/3/4 vs PgPreparedStatement
nuance and dropped the imprecise "unchanged back through 9.2" claim.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

skill(apm-integrations): make the family-placement example repo-generic (standalone)

The skill must be readable by any agent working on dd-trace-java integrations, independent of
the eval toolkit. Reworded the Cassandra family-placement example to drop eval-specific framing
("regen", "R-DB-1", "the eval was given the slug", "blind protocol") and state it as a plain
worked example. Same lesson: a name taken by an existing family means join that family, not create
a parallel module; the harm is duplicate advice on the same types.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Co-authored-by: devflow.devflow-routing-intake <devflow.devflow-routing-intake@kubernetes.us1.ddbuild.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tag: ai generated Largely based on code generated by an AI or LLM tag: apm integration toolkit Changes generated by DataDog/apm-instrumentation-toolkit as part of IDM AIT experimentation tag: do not merge Do not merge changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants