Skip to content

refactor(generator): make descriptor invariants structural — delete the self-referential validation apparatus - #28

Merged
ANcpLua merged 13 commits into
mainfrom
refactor/descriptor-metadata-root-fix
Jul 2, 2026
Merged

refactor(generator): make descriptor invariants structural — delete the self-referential validation apparatus#28
ANcpLua merged 13 commits into
mainfrom
refactor/descriptor-metadata-root-fix

Conversation

@ANcpLua

@ANcpLua ANcpLua commented Jul 1, 2026

Copy link
Copy Markdown
Owner

What

Audit finding: every read of TargetKindMask, matcher ContractKeys, matcher/emission Family/MethodShape, the three policy enums (SignalOwnership/ErrorPolicy/DurationPolicy), and the target's three Matcher* fields sat inside validators that compared static data against a redundant copy of itself. ValidateEmissionDescriptorPolicy itself proved the policy enums are fully determined by the body type — denormalized derived data, stored 29×, then runtime-checked for consistency on every consumer build.

This PR makes the invariants structural instead of runtime-checked:

  • Body hierarchy: 8 optional IsDefined-structs → abstract record InterceptorBodyDescriptor + 8 sealed records. An emission row is now (Kind, Body); exactly-one-body and policy consistency are unrepresentable as errors.
  • Matcher rows shrink to (Name, ReceiverTypePattern, TryMatch) — 8 constructors → 2. The declaration metadata (kind masks, contract-key lists) existed only to be validated against what the matchers actually produce.
  • Emitter dispatch is a type switch on the body descriptor.
  • Deleted, not relocated (~370 lines): ValidateDescriptorCatalog + its Initialize() call (the CS8785 × TreatWarningsAsErrors=true consumer-build-break vector), EnsureTargetDeclaredByMatcher/EnsureKindDeclaredByMatcher/EnsureContractDeclaredByMatcher, EnsureEmissionDescriptorMatchesMatcher, ValidateEmissionDescriptorPolicy, ValidateSingleBodyDescriptor, ValidateMethodShape ×2, ValidatePolicy, InterceptorKinds()/GetInterceptorKindMask bitmask machinery, and 5 enums.
  • DB contract keys single-sourced: GetDbTraceContractKey was "signals.traces." + id re-enumerated as a switch (its default arm was unreachable — silent-ADONET before 544ee8d, throw after). The id set now lives only in GetDbInstrumentationId; the key is derived. Both default arms are gone.
  • tools/verify-contract-invariants.py rewritten where it pinned the deleted validators as required tokens or re-derived ownership only to compare against the declared enum. The one real invariant ValidateDescriptorCatalog carried — every InterceptorKind maps to exactly one emission row — is now enforced test-time by parse_emission_descriptor_bodies (duplicates fail), not on every consumer build.

GetInterceptorReceiverSurface() / the TCG interceptorReceivers section is deliberately kept (consumed by TelemetryCapabilityGraphGenerator).

Why

The generator carried a self-referential validation layer: metadata that exists only to be validated, validated by code that exists only because the metadata is redundant. Under TreatWarningsAsErrors=true the Initialize()-time throw was also the repo's only realistic consumer-build-break vector (CS8785 → error). Root fix over symptom fix: make the invariants structural and the whole layer deletes itself.

Verification — complete and verified

  • Full solution build: 0 warnings / 0 errors (TWAE=true)
  • tools/verify-generator-snapshots.py: generator-snapshots-ok — emitted source is byte-identical; this refactor changes zero generated bytes
  • tools/verify-contract-invariants.py: contract-invariants-ok
  • Runtime smoke: real-aspnetcore-demo-ok, real-ilogger-demo-ok

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings July 1, 2026 22:23
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ef61488d-6585-483c-8c4f-a47474fb5a05

📥 Commits

Reviewing files that changed from the base of the PR and between adf9527 and 52ae79b.

📒 Files selected for processing (6)
  • .claude/TASK.md
  • AGENTS.md
  • CLAUDE.md
  • CLAUDE.md
  • docs/TELEMETRY_CAPABILITY_GRAPH.md
  • src/Qyl.OpenTelemetry.AutoInstrumentation/QylAspNetCoreInstrumentationServiceCollectionExtensions.cs
💤 Files with no reviewable changes (2)
  • AGENTS.md
  • .claude/TASK.md
📜 Recent review details
🧰 Additional context used
📓 Path-based instructions (6)
CLAUDE.md

📄 CodeRabbit inference engine (CLAUDE.md)

CLAUDE.md is the single agent-rules file for this repository.

Files:

  • CLAUDE.md
**

⚙️ CodeRabbit configuration file

AGENTS.md

**: # Qyl.OpenTelemetry.AutoInstrumentation agent rules

Mission

This repository is the runtime AOT auto-instrumentation lane for qyl, evolving into a
self-describing observability substrate. The foundation is unchanged: .NET 10
NativeAOT-compatible zero-code instrumentation through managed build assets, source generation,
DiagnosticListener consumption, and module-initializer boot. The direction is the North Star
below.

Keep this repository separate from:

  • semantic-convention package generation (Qyl.OpenTelemetry.SemanticConventions is a referenced
    vocabulary package, not generated here),
  • the old CLR-profiler/OpenTelemetry auto-instrumentation substrate.

North Star — declare and prove the whole stack

Every observability tool today is pull-by-observation: a backend learns what a service emits
by receiving samples over time, and never knows whether it has seen the whole surface. qyl has a
capability none of them have — because instrumentation is source-generated interceptors + a static
contract + a referenced semconv registry + (incrementally) DTO inference, the complete set of
telemetry a binary can ever produce is a compile-time-derivable fact, with provenance.

The substrate goal: every qyl binary ships a complete, machine-readable Telemetry Capability
Graph (TCG)
— the full possible OpenTelemetry surface for that exact binary, each capability
tagged compile-time-owned vs runtime-valued — and proves it by self-hosting (instrumenting its
own pipeline with its own mechanism, zero extra code). Any external entity consumes the TCG to know
the entire stack before a span is sampled. The contract becomes the shared semantic graph; an OTLP
backend is just one consumer.

Three pillars:

  1. Self-host (the proof). qyl instruments qyl with qyl — QylSelfTelemetry /
    SemConvConformanceProcessor are the seed; the binary observing itself is how "declared TCG ==
    runtime reality" is checked.
  2. **Compile-time-complete ...

Files:

  • CLAUDE.md
  • docs/TELEMETRY_CAPABILITY_GRAPH.md
  • src/Qyl.OpenTelemetry.AutoInstrumentation/QylAspNetCoreInstrumentationServiceCollectionExtensions.cs

⚙️ CodeRabbit configuration file

**: Operating principles (solo-dev, agentic SDLC — reviews are advisory, agents act on them):

  1. LAZY: one self-contained, correct review beats ten partial ones. Every finding is
    definitive — concrete evidence with file:line, a concrete fix, no "consider maybe",
    no open or ambiguous questions back to the author. If you cannot decide a point
    from the diff plus repo context, stay silent on it. Never cite a source, API, or
    version you have not verified; an unverifiable claim is a dropped claim.
  2. IMPATIENT: never stall a PR. There are no compatibility obligations here — internal
    and dogfooding code has NO public-API contract; removing shims, breaking signatures,
    and deleting dead paths are normal, desirable changes. Do not flag backward
    compatibility, deprecation ceremony, or migration paths. (SemVer applies only to
    commercially sold libraries — this repo has none.)
  3. EGO: hold the bar of the best reviewer on the market — flag real correctness,
    security, data-loss, and structural problems precisely; produce zero noise.

Files:

  • CLAUDE.md
  • docs/TELEMETRY_CAPABILITY_GRAPH.md
  • src/Qyl.OpenTelemetry.AutoInstrumentation/QylAspNetCoreInstrumentationServiceCollectionExtensions.cs
docs/TELEMETRY_CAPABILITY_GRAPH.md

📄 CodeRabbit inference engine (CLAUDE.md)

The Telemetry Capability Graph documentation must describe the current-tree behavior of the shipped TCG surface, not future pillars or old PR-state claims.

Files:

  • docs/TELEMETRY_CAPABILITY_GRAPH.md
**/*.{cs,csproj}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{cs,csproj}: Interceptors are the right tool only where no runtime seam exists; where ASP.NET Core (or any host) offers a first-class seam such as IStartupFilter or DI, use the seam instead of an intercepted call site.
Do not reintroduce a Build()/CreateBuilder() interceptor or any cross-generator coordination protocol (MSBuild opt-out knobs, reference sniffing, compose wrappers) to arbitrate call-site ownership.
The accepted qyl instrumentation mechanisms are limited to ordinary C# compiled into the app, source-generated interceptors, build-transitive assets, module-initializer activation, BCL telemetry primitives, and public library diagnostic payloads; do not add other instrumentation mechanisms into product code or package assets.
Runtime listeners must consume framework/library DiagnosticListener payloads and never synthesize missing values; missing values stay missing.
The ASP.NET Core server span should be implemented as middleware registered via IStartupFilter in AddQylAspNetCoreInstrumentation(), with per-signal ownership arbitrated at runtime by QylSignalOwnership so exactly one owner exists per signal per process.

Files:

  • src/Qyl.OpenTelemetry.AutoInstrumentation/QylAspNetCoreInstrumentationServiceCollectionExtensions.cs
src/Qyl.OpenTelemetry.AutoInstrumentation/**/*.{cs,csproj}

📄 CodeRabbit inference engine (CLAUDE.md)

Core shared runtime helpers belong in Qyl.OpenTelemetry.AutoInstrumentation, and this package must remain dependency-light without leaking EF Core or SqlClient dependencies, warnings, or NativeAOT constraints.

Files:

  • src/Qyl.OpenTelemetry.AutoInstrumentation/QylAspNetCoreInstrumentationServiceCollectionExtensions.cs
src/**/*.cs

⚙️ CodeRabbit configuration file

src/**/*.cs: Zero-code instrumentation runtime: this code runs inside EVERY request of host
applications. Top priorities, in order: (1) allocations and boxing on hot paths —
flag closures, LINQ, params arrays, string concat in listener/semantic-tag code;
(2) tag cardinality — any attribute value that is unbounded (raw URLs, user input,
exception messages) explodes at scale; (3) Activity/Meter lifecycle — undisposed
listeners, leaked subscriptions, double-Start/Stop; (4) thread safety of shared
listener state. PublicAPI.Shipped/Unshipped.txt are analyzer-managed: edits must
come from the analyzer flow, and API breaks are fine (internal product, no
compatibility contract).

Files:

  • src/Qyl.OpenTelemetry.AutoInstrumentation/QylAspNetCoreInstrumentationServiceCollectionExtensions.cs
🔇 Additional comments (3)
CLAUDE.md (1)

1-255: LGTM!

docs/TELEMETRY_CAPABILITY_GRAPH.md (1)

116-118: LGTM!

src/Qyl.OpenTelemetry.AutoInstrumentation/QylAspNetCoreInstrumentationServiceCollectionExtensions.cs (1)

13-17: LGTM!

Also applies to: 33-34


📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added generated telemetry capability graph support for interceptor receiver information.
    • Introduced updated ASP.NET Core guidance for safe middleware registration alongside zero-config instrumentation.
  • Bug Fixes

    • Improved interceptor matching and emission handling for more predictable runtime behavior.
    • Updated database and gRPC-related telemetry handling to align with the latest generated metadata.
  • Documentation

    • Refreshed repository and telemetry graph documentation.
    • Replaced obsolete agent guidance with the current project rules document.

Walkthrough

The PR replaces interceptor descriptor policy flags with typed body descriptors, rewires generator dispatch and detection around that model, updates invariant checks and manifest output, and refreshes repository guidance plus related docs and fixtures.

Changes

Generator and verification refactor

Layer / File(s) Summary
Descriptor model
src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Descriptors.cs
InterceptorKind becomes the sole category enum, interceptor emissions carry a single typed body descriptor, matcher constructor behavior changes, and matcher identity fields are removed from targets.
Catalog and manifest projection
src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylGeneratedSourceInterceptorCatalog.cs, src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/TelemetryCapabilityGraphGenerator.cs
Generated matcher and emission descriptors are rebuilt with the new typed shape, and the capability graph manifest adds an interceptorReceivers array.
Generator dispatch
src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.cs, src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Shapes.cs, src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Detection.cs
Startup validation is removed, matching returns on first hit, emission dispatch switches on typed bodies, trace helper signatures drop in, DB trace key handling changes, and request-delegate interception is removed.
Contract invariants
tools/verify-contract-invariants.py
The verifier is updated to check typed emission bodies, revised DB trace keys, and the new generator key and dispatch shape.
Evidence update
tools/Qyl.OpenTelemetry.AutoInstrumentation.OtlpFixtures/verified/webapi-aot-traces.otlp.json
The verified probe span attributes are updated to the new ASP.NET Core domain and URL scheme.
Task plan
.claude/TASK.md
The task document content is replaced.

Repository guidance and evidence

Layer / File(s) Summary
Guidance and reference docs
AGENTS.md, CLAUDE.md, docs/TELEMETRY_CAPABILITY_GRAPH.md
The old guidance file is removed, the new repository guidance file is added, and the telemetry capability graph docs point to it.
Service comments and fixture
src/Qyl.OpenTelemetry.AutoInstrumentation/QylAspNetCoreInstrumentationServiceCollectionExtensions.cs, tools/Qyl.OpenTelemetry.AutoInstrumentation.OtlpFixtures/verified/webapi-aot-traces.otlp.json
ASP.NET Core registration remarks/comments are rewritten, and the verified OTLP span attributes change for the probe request.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the generator refactor and removal of the self-referential validation layer.
Description check ✅ Passed The description accurately covers the structural descriptor refactor, validator removal, DB key simplification, and verification updates.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/descriptor-metadata-root-fix
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch refactor/descriptor-metadata-root-fix

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

Copilot AI 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.

Pull request overview

Refactors the source generator’s interceptor descriptor model to make previously runtime-validated invariants structural (via a closed body-descriptor hierarchy), removing a large self-referential validation layer and simplifying matcher/emission descriptor metadata. This aligns with the repo’s goal of keeping generator output deterministic while reducing consumer-build fragility.

Changes:

  • Replaces “optional IsDefined structs + policy enums + validator apparatus” with a closed InterceptorBodyDescriptor record hierarchy and type-switch emitter dispatch.
  • Slims matcher descriptors down to (Name, ReceiverTypePattern, TryMatch) and removes kind-mask / contract-key / family / method-shape metadata that existed only for validation.
  • Updates tools/verify-contract-invariants.py to enforce the remaining real invariants (unique kind→body mapping, DB key derivation) without pinning deleted validator tokens.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tools/verify-contract-invariants.py Updates invariant checks to match the new structural descriptor model and DB trace key derivation.
src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/TelemetryCapabilityGraphGenerator.cs Adds interceptorReceivers emission to the TCG manifest JSON.
src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylGeneratedSourceInterceptorCatalog.cs Rewrites generated matcher/emission descriptor construction and exposes receiver surface for TCG.
src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Shapes.cs Removes DB trace-key switch helper and kind-mask machinery; updates call sites accordingly.
src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Detection.cs Derives DB trace contract keys directly from instrumentationId.
src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Descriptors.cs Introduces InterceptorBodyDescriptor hierarchy; removes policy enums and matcher metadata fields.
src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.cs Deletes descriptor catalog validation and matcher/emission cross-checks; switches emitter dispatch to body-type pattern matching.
.claude/TASK.md Updates task documentation to reflect the new refactor plan and evidence.

ANcpLua and others added 7 commits July 2, 2026 00:29
…ontractKey

The default arm silently mapped any unlisted instrumentationId to
signals.traces.ADONET — a false trace-contract identity, inconsistent
with the sibling GetDbMetricContractKeys (empty default) and the
codebase-wide 'unknown -> throw' invariant. All 7 ids GetDbInstrumentationId
can return are enumerated explicitly, so the default is unreachable today;
this makes a future unlisted provider fail loudly instead of misfiling.

Verified: full solution build green (0/0); AspNetCore + ILogger runtime
interception verifiers still pass.

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

InterceptorMatcherDescriptor.ReceiverTypePattern was a curated 25-row
registry (incl. wildcard/pipe patterns like 'Azure.*Client' and the
Kafka/MassTransit/Elastic unions) that nothing read — dead metadata.
Rather than delete a curated column that is not recoverable from the
matcher delegates, make it live: GetInterceptorReceiverSurface() reads
it and the Telemetry Capability Graph now emits an 'interceptorReceivers'
section, giving consumers a machine-readable map of the exact receiver
surface qyl intercepts.

Verified: solution build green (0/0); generator-snapshots-ok;
tcg-publishing-demo-ok.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ta (cut 1/4)

Delete the five validation-only enums (EmitterFamily, MethodShape,
SignalOwnership, ErrorPolicy, DurationPolicy) — every read of them was
inside validators that checked static data against redundant copies of
itself. The 8 body descriptors become a closed sealed-record hierarchy
under InterceptorBodyDescriptor; exactly-one-body is now structural.
Matcher descriptor: 8 ctors -> 2, declaration metadata dropped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e validation apparatus (cuts 2-4)

Every read of TargetKindMask, matcher ContractKeys, matcher/emission
Family and MethodShape, the three policy enums, and the target's three
Matcher* fields was inside validators comparing static data against a
redundant copy of itself. With the body hierarchy from cut 1:

- emission catalog rows shrink to (Kind, Body); exactly-one-body and
  policy consistency are unrepresentable as errors, not runtime-checked
- matcher rows shrink to (Name, ReceiverTypePattern, TryMatch)
- emitter dispatch is a type switch on the body descriptor
- ValidateDescriptorCatalog, Ensure* trio, ValidateEmissionDescriptorPolicy,
  ValidateSingleBodyDescriptor, ValidateMethodShape x2, ValidatePolicy and
  the Initialize()-time throw (the CS8785/TreatWarningsAsErrors build-break
  vector) are deleted, not relocated
- GetDbTraceContractKey was 'signals.traces.' + id re-enumerated as a
  switch; the id set now lives only in GetDbInstrumentationId and the
  contract key is derived, so the unreachable default arm (silent-ADONET
  before 544ee8d, throw after) is gone entirely
- InterceptorKinds()/GetInterceptorKindMask bitmask machinery deleted

Verified: solution build 0/0 (TWAE), generator-snapshots-ok
(byte-identical emitted source), real-aspnetcore-demo-ok,
real-ilogger-demo-ok.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ant-representation checks

The python harness pinned the deleted validators as required tokens and
re-derived SignalOwnership from target contract keys only to compare it
against the declared enum — consistency checks between two encodings of
one fact. Now that one encoding remains:

- new parse_emission_descriptor_bodies: every InterceptorKind maps to
  exactly one typed body row, duplicates fail (the one real invariant
  ValidateDescriptorCatalog carried, now test-time instead of consumer
  build-time)
- emitter dispatch check follows the body-type switch
- DB trace contract keys derive from GetDbInstrumentationId (single
  source) instead of the deleted GetDbTraceContractKey switch
- ownership/policy-matrix checks deleted with the enums they mirrored

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

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/TASK.md:
- Line 4: The TASK.md header has an inconsistent Started date because the
current value is in the future. Update the Started field in the task header to
the actual start date so it matches the review date and keep the same metadata
format.
- Around line 53-60: Add the missing blank lines around the markdown headings in
the TASK document so the lint passes: ensure `## Non-goals` and `## Resume
notes` each have a blank line before and after them, while leaving the existing
content and the non-goals/resume notes text unchanged.

In
`@src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylGeneratedSourceInterceptorCatalog.cs`:
- Line 21: The NServiceBus interceptor manifest is advertising the wrong
receiver surface because QylGeneratedSourceInterceptorCatalog’s NServiceBus
matcher string and serialization path are out of sync with
TryGetNServiceBusInvocation. Update the descriptor and the TCG serialization so
they reflect the actual supported types for this binary, using the same
NServiceBus surface that TryGetNServiceBusInvocation accepts and emits. Make
sure the catalog entry and the generated manifest stay aligned so the published
capability graph documents current-tree behavior rather than a stale
IEndpointInstance/IPipelineContext surface.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7757c443-a71a-4eb1-8106-f8b8b34e9bf3

📥 Commits

Reviewing files that changed from the base of the PR and between bc6d5d7 and 2f68501.

📒 Files selected for processing (8)
  • .claude/TASK.md
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Descriptors.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Detection.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Shapes.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylGeneratedSourceInterceptorCatalog.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/TelemetryCapabilityGraphGenerator.cs
  • tools/verify-contract-invariants.py
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: copilot-pull-request-reviewer
  • GitHub Check: verify (qyl-macos)
🧰 Additional context used
📓 Path-based instructions (2)
**

⚙️ CodeRabbit configuration file

AGENTS.md

**: # Qyl.OpenTelemetry.AutoInstrumentation agent rules

Mission

This repository is the runtime AOT auto-instrumentation lane for qyl, evolving into a
self-describing observability substrate. The foundation is unchanged: .NET 10
NativeAOT-compatible zero-code instrumentation through managed build assets, source generation,
DiagnosticListener consumption, and module-initializer boot. The direction is the North Star
below.

Keep this repository separate from:

  • semantic-convention package generation (Qyl.OpenTelemetry.SemanticConventions is a referenced
    vocabulary package, not generated here),
  • the old CLR-profiler/OpenTelemetry auto-instrumentation substrate.

North Star — declare and prove the whole stack

Every observability tool today is pull-by-observation: a backend learns what a service emits
by receiving samples over time, and never knows whether it has seen the whole surface. qyl has a
capability none of them have — because instrumentation is source-generated interceptors + a static
contract + a referenced semconv registry + (incrementally) DTO inference, the complete set of
telemetry a binary can ever produce is a compile-time-derivable fact, with provenance.

The substrate goal: every qyl binary ships a complete, machine-readable Telemetry Capability
Graph (TCG)
— the full possible OpenTelemetry surface for that exact binary, each capability
tagged compile-time-owned vs runtime-valued — and proves it by self-hosting (instrumenting its
own pipeline with its own mechanism, zero extra code). Any external entity consumes the TCG to know
the entire stack before a span is sampled. The contract becomes the shared semantic graph; an OTLP
backend is just one consumer.

Three pillars:

  1. Self-host (the proof). qyl instruments qyl with qyl — QylSelfTelemetry /
    SemConvConformanceProcessor are the seed; the binary observing itself is how "declared TCG ==
    runtime reality" is checked.
  2. **Compile-time-complete ...

Files:

  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Detection.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/TelemetryCapabilityGraphGenerator.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Shapes.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylGeneratedSourceInterceptorCatalog.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Descriptors.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.cs
  • tools/verify-contract-invariants.py

⚙️ CodeRabbit configuration file

**: Operating principles (solo-dev, agentic SDLC — reviews are advisory, agents act on them):

  1. LAZY: one self-contained, correct review beats ten partial ones. Every finding is
    definitive — concrete evidence with file:line, a concrete fix, no "consider maybe",
    no open or ambiguous questions back to the author. If you cannot decide a point
    from the diff plus repo context, stay silent on it. Never cite a source, API, or
    version you have not verified; an unverifiable claim is a dropped claim.
  2. IMPATIENT: never stall a PR. There are no compatibility obligations here — internal
    and dogfooding code has NO public-API contract; removing shims, breaking signatures,
    and deleting dead paths are normal, desirable changes. Do not flag backward
    compatibility, deprecation ceremony, or migration paths. (SemVer applies only to
    commercially sold libraries — this repo has none.)
  3. EGO: hold the bar of the best reviewer on the market — flag real correctness,
    security, data-loss, and structural problems precisely; produce zero noise.

Files:

  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Detection.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/TelemetryCapabilityGraphGenerator.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Shapes.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylGeneratedSourceInterceptorCatalog.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Descriptors.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.cs
  • tools/verify-contract-invariants.py
src/**/*.cs

⚙️ CodeRabbit configuration file

src/**/*.cs: Zero-code instrumentation runtime: this code runs inside EVERY request of host
applications. Top priorities, in order: (1) allocations and boxing on hot paths —
flag closures, LINQ, params arrays, string concat in listener/semantic-tag code;
(2) tag cardinality — any attribute value that is unbounded (raw URLs, user input,
exception messages) explodes at scale; (3) Activity/Meter lifecycle — undisposed
listeners, leaked subscriptions, double-Start/Stop; (4) thread safety of shared
listener state. PublicAPI.Shipped/Unshipped.txt are analyzer-managed: edits must
come from the analyzer flow, and API breaks are fine (internal product, no
compatibility contract).

Files:

  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Detection.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/TelemetryCapabilityGraphGenerator.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Shapes.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylGeneratedSourceInterceptorCatalog.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Descriptors.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.cs
🪛 markdownlint-cli2 (0.22.1)
.claude/TASK.md

[warning] 53-53: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 58-58: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🔇 Additional comments (7)
tools/verify-contract-invariants.py (1)

1352-1396: LGTM!

Also applies to: 1446-1458, 1467-1467, 1491-1504, 1538-1538, 1554-1554, 1587-1587

src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Descriptors.cs (1)

10-41: LGTM!

Also applies to: 93-150, 263-344

src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.cs (1)

65-85: LGTM!

Also applies to: 142-222, 253-262, 309-341, 360-386, 404-435, 551-555

src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Detection.cs (1)

146-146: LGTM!

src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Shapes.cs (1)

96-107: LGTM!

Also applies to: 855-856

src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylGeneratedSourceInterceptorCatalog.cs (1)

9-20: LGTM!

Also applies to: 22-97

src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/TelemetryCapabilityGraphGenerator.cs (1)

119-132: LGTM!

Comment thread .claude/TASK.md Outdated
Comment thread .claude/TASK.md Outdated
…v changes

PR #23/#25 changed the aspnetcore server-span shape on main
(qyl.instrumentation.domain http.server -> aspnetcore.server; new
url.scheme attribute) without regenerating this fixture, and main's own
verify runs are still queued — so any PR merge-commit run fails 'otlp
verified fixtures'. Regenerated via verify-otlp-fixtures.py
--update-verified; the diff is exactly those two attribute changes,
unrelated to the descriptor refactor (generator snapshots byte-identical).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ANcpLua
ANcpLua force-pushed the refactor/descriptor-metadata-root-fix branch from 2f68501 to 3d74049 Compare July 1, 2026 22:30
…ly accepts

IsNServiceBusEndpointType matches IMessageSession | IMessageHandlerContext;
the curated pattern still claimed IMessageSession | IEndpointInstance |
IPipelineContext from an earlier design. Now that GetInterceptorReceiverSurface
serializes the pattern into the Telemetry Capability Graph, the string must
tell the truth. All other 24 patterns spot-checked against their matchers - only
NServiceBus drifted. Also MD022 blank lines in the task file.

Reported-by: coderabbitai
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylGeneratedSourceInterceptorCatalog.cs`:
- Around line 11-30: Update QylGeneratedSourceInterceptorCatalog so the exported
receiver-surface metadata matches what the interceptor logic can actually emit.
The entries for AspNetCoreEndpointMap, LoggerExtensions, and MongoDb should
advertise the real receiver types used by the corresponding
TryGetAspNetCoreEndpointMapInvocation, TryGetLoggerExtensionInvocation, and
TryGetMongoDbInvocation logic, not the matcher labels; also remove the dead
AspNetCoreRequestDelegate surface entry since the RequestDelegate.Invoke
MethodKind guard makes it non-matchable. Keep the receiver surface explicit and
aligned with each InterceptorTarget.ReceiverType so
GetInterceptorReceiverSurface reflects current-tree behavior only.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7d7464b5-c3ec-41ea-b948-22a180264a87

📥 Commits

Reviewing files that changed from the base of the PR and between 2f68501 and d634f2d.

📒 Files selected for processing (9)
  • .claude/TASK.md
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Descriptors.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Detection.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Shapes.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylGeneratedSourceInterceptorCatalog.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/TelemetryCapabilityGraphGenerator.cs
  • tools/Qyl.OpenTelemetry.AutoInstrumentation.OtlpFixtures/verified/webapi-aot-traces.otlp.json
  • tools/verify-contract-invariants.py
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: verify (qyl-macos)
🧰 Additional context used
📓 Path-based instructions (2)
**

⚙️ CodeRabbit configuration file

AGENTS.md

**: # Qyl.OpenTelemetry.AutoInstrumentation agent rules

Mission

This repository is the runtime AOT auto-instrumentation lane for qyl, evolving into a
self-describing observability substrate. The foundation is unchanged: .NET 10
NativeAOT-compatible zero-code instrumentation through managed build assets, source generation,
DiagnosticListener consumption, and module-initializer boot. The direction is the North Star
below.

Keep this repository separate from:

  • semantic-convention package generation (Qyl.OpenTelemetry.SemanticConventions is a referenced
    vocabulary package, not generated here),
  • the old CLR-profiler/OpenTelemetry auto-instrumentation substrate.

North Star — declare and prove the whole stack

Every observability tool today is pull-by-observation: a backend learns what a service emits
by receiving samples over time, and never knows whether it has seen the whole surface. qyl has a
capability none of them have — because instrumentation is source-generated interceptors + a static
contract + a referenced semconv registry + (incrementally) DTO inference, the complete set of
telemetry a binary can ever produce is a compile-time-derivable fact, with provenance.

The substrate goal: every qyl binary ships a complete, machine-readable Telemetry Capability
Graph (TCG)
— the full possible OpenTelemetry surface for that exact binary, each capability
tagged compile-time-owned vs runtime-valued — and proves it by self-hosting (instrumenting its
own pipeline with its own mechanism, zero extra code). Any external entity consumes the TCG to know
the entire stack before a span is sampled. The contract becomes the shared semantic graph; an OTLP
backend is just one consumer.

Three pillars:

  1. Self-host (the proof). qyl instruments qyl with qyl — QylSelfTelemetry /
    SemConvConformanceProcessor are the seed; the binary observing itself is how "declared TCG ==
    runtime reality" is checked.
  2. **Compile-time-complete ...

Files:

  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Detection.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/TelemetryCapabilityGraphGenerator.cs
  • tools/Qyl.OpenTelemetry.AutoInstrumentation.OtlpFixtures/verified/webapi-aot-traces.otlp.json
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylGeneratedSourceInterceptorCatalog.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Shapes.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.cs
  • tools/verify-contract-invariants.py
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Descriptors.cs

⚙️ CodeRabbit configuration file

**: Operating principles (solo-dev, agentic SDLC — reviews are advisory, agents act on them):

  1. LAZY: one self-contained, correct review beats ten partial ones. Every finding is
    definitive — concrete evidence with file:line, a concrete fix, no "consider maybe",
    no open or ambiguous questions back to the author. If you cannot decide a point
    from the diff plus repo context, stay silent on it. Never cite a source, API, or
    version you have not verified; an unverifiable claim is a dropped claim.
  2. IMPATIENT: never stall a PR. There are no compatibility obligations here — internal
    and dogfooding code has NO public-API contract; removing shims, breaking signatures,
    and deleting dead paths are normal, desirable changes. Do not flag backward
    compatibility, deprecation ceremony, or migration paths. (SemVer applies only to
    commercially sold libraries — this repo has none.)
  3. EGO: hold the bar of the best reviewer on the market — flag real correctness,
    security, data-loss, and structural problems precisely; produce zero noise.

Files:

  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Detection.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/TelemetryCapabilityGraphGenerator.cs
  • tools/Qyl.OpenTelemetry.AutoInstrumentation.OtlpFixtures/verified/webapi-aot-traces.otlp.json
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylGeneratedSourceInterceptorCatalog.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Shapes.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.cs
  • tools/verify-contract-invariants.py
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Descriptors.cs
src/**/*.cs

⚙️ CodeRabbit configuration file

src/**/*.cs: Zero-code instrumentation runtime: this code runs inside EVERY request of host
applications. Top priorities, in order: (1) allocations and boxing on hot paths —
flag closures, LINQ, params arrays, string concat in listener/semantic-tag code;
(2) tag cardinality — any attribute value that is unbounded (raw URLs, user input,
exception messages) explodes at scale; (3) Activity/Meter lifecycle — undisposed
listeners, leaked subscriptions, double-Start/Stop; (4) thread safety of shared
listener state. PublicAPI.Shipped/Unshipped.txt are analyzer-managed: edits must
come from the analyzer flow, and API breaks are fine (internal product, no
compatibility contract).

Files:

  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Detection.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/TelemetryCapabilityGraphGenerator.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylGeneratedSourceInterceptorCatalog.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Shapes.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Descriptors.cs
🔇 Additional comments (3)
.claude/TASK.md (1)

4-4: Fix the Started date.

2026-07-02 is still in the future for this review date (2026-07-01). Update the header to the actual start date.

tools/verify-contract-invariants.py (1)

1352-1396: LGTM!

Also applies to: 1446-1458, 1467-1467, 1491-1504, 1538-1538, 1554-1554, 1587-1587

tools/Qyl.OpenTelemetry.AutoInstrumentation.OtlpFixtures/verified/webapi-aot-traces.otlp.json (1)

24-24: LGTM!

Also applies to: 56-62

Comment on lines +11 to +30
new InterceptorMatcherDescriptor("AspNetCoreRequestDelegate", "global::Microsoft.AspNetCore.Http.RequestDelegate", TryGetAspNetCoreRequestDelegateInvocation),
new InterceptorMatcherDescriptor("AspNetCoreEndpointMap", "global::Microsoft.AspNetCore.Builder.EndpointRouteBuilderExtensions", TryGetAspNetCoreEndpointMapInvocation),
new InterceptorMatcherDescriptor("MeterProviderBuilderAddMeter", "global::OpenTelemetry.Metrics.MeterProviderBuilder", TryGetMeterProviderBuilderAddMeterInvocation),
new InterceptorMatcherDescriptor("AzureClient", "Azure.*Client", TryGetAzureClientInvocation),
new InterceptorMatcherDescriptor("Elastic", "Elastic.Clients.Elasticsearch.*Client|Elastic.Transport.ITransport", TryGetElasticInvocation),
new InterceptorMatcherDescriptor("WcfClient", "global::System.ServiceModel.ClientBase<TChannel>", TryGetWcfClientInvocation),
new InterceptorMatcherDescriptor("GrpcNetClientUnary", "global::Grpc.Core.ClientBase<T>", TryGetGrpcNetClientAsyncUnaryInvocation),
new InterceptorMatcherDescriptor("GrpcNetClientStreaming", "global::Grpc.Core.ClientBase<T>", TryGetGrpcNetClientStreamingInvocation),
new InterceptorMatcherDescriptor("Kafka", "Confluent.Kafka.IProducer<TKey,TValue>|Confluent.Kafka.IConsumer<TKey,TValue>", TryGetKafkaInvocation),
new InterceptorMatcherDescriptor("MassTransit", "MassTransit.IPublishEndpoint|MassTransit.ISendEndpoint|MassTransit.ISendEndpointProvider", TryGetMassTransitInvocation),
new InterceptorMatcherDescriptor("NServiceBus", "NServiceBus.IMessageSession|NServiceBus.IMessageHandlerContext", TryGetNServiceBusInvocation),
new InterceptorMatcherDescriptor("Quartz", "Quartz.IJob", TryGetQuartzInvocation),
new InterceptorMatcherDescriptor("StackExchangeRedis", "StackExchange.Redis.IDatabase", TryGetStackExchangeRedisInvocation),
new InterceptorMatcherDescriptor("GraphQL", "GraphQL.IDocumentExecuter", TryGetGraphQlInvocation),
new InterceptorMatcherDescriptor("EntityFrameworkCoreDbContext", "global::Microsoft.EntityFrameworkCore.DbContext", TryGetEntityFrameworkCoreDbContextInvocation),
new InterceptorMatcherDescriptor("EntityFrameworkCoreQueryable", "global::Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions", TryGetEntityFrameworkCoreQueryableInvocation),
new InterceptorMatcherDescriptor("MongoDb", "MongoDB.Driver.IMongoCollection<TDocument>", TryGetMongoDbInvocation),
new InterceptorMatcherDescriptor("DbCommand", "global::System.Data.Common.DbCommand", TryGetDbCommandInvocation),
new InterceptorMatcherDescriptor("RabbitMq", "RabbitMQ.Client.IModel|RabbitMQ.Client.IChannel", TryGetRabbitMqInvocation),
new InterceptorMatcherDescriptor("LoggerExtensions", "global::Microsoft.Extensions.Logging.LoggerExtensions", TryGetLoggerExtensionInvocation),

@coderabbitai coderabbitai Bot Jul 1, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not publish matcher labels as the receiver surface.

GetInterceptorReceiverSurface() serializes ReceiverTypePattern, but several entries are not the emitted receiver surface: Line 12 publishes EndpointRouteBuilderExtensions while detection emits IEndpointRouteBuilder, Line 30 publishes LoggerExtensions while detection emits ILogger, Line 23 publishes IDatabase while detection gates IDatabaseAsync, and Line 11 is intentionally non-matchable due the RequestDelegate.Invoke MethodKind guard. The TCG will advertise receivers this binary cannot actually produce. Make receiver-surface metadata explicit and aligned with each InterceptorTarget.ReceiverType, and drop the dead RequestDelegate receiver from the exported surface.

Targeted fix shape
-            new InterceptorMatcherDescriptor("AspNetCoreRequestDelegate", "global::Microsoft.AspNetCore.Http.RequestDelegate", TryGetAspNetCoreRequestDelegateInvocation),
-            new InterceptorMatcherDescriptor("AspNetCoreEndpointMap", "global::Microsoft.AspNetCore.Builder.EndpointRouteBuilderExtensions", TryGetAspNetCoreEndpointMapInvocation),
+            new InterceptorMatcherDescriptor("AspNetCoreEndpointMap", "global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder", TryGetAspNetCoreEndpointMapInvocation),
...
-            new InterceptorMatcherDescriptor("StackExchangeRedis", "StackExchange.Redis.IDatabase", TryGetStackExchangeRedisInvocation),
+            new InterceptorMatcherDescriptor("StackExchangeRedis", "StackExchange.Redis.IDatabaseAsync", TryGetStackExchangeRedisInvocation),
...
-            new InterceptorMatcherDescriptor("LoggerExtensions", "global::Microsoft.Extensions.Logging.LoggerExtensions", TryGetLoggerExtensionInvocation),
+            new InterceptorMatcherDescriptor("LoggerExtensions", "global::Microsoft.Extensions.Logging.ILogger", TryGetLoggerExtensionInvocation),

As per coding guidelines, the TCG is “the full possible OpenTelemetry surface for that exact binary” and documentation must “Document current-tree behavior.”

Also applies to: 90-96

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylGeneratedSourceInterceptorCatalog.cs`
around lines 11 - 30, Update QylGeneratedSourceInterceptorCatalog so the
exported receiver-surface metadata matches what the interceptor logic can
actually emit. The entries for AspNetCoreEndpointMap, LoggerExtensions, and
MongoDb should advertise the real receiver types used by the corresponding
TryGetAspNetCoreEndpointMapInvocation, TryGetLoggerExtensionInvocation, and
TryGetMongoDbInvocation logic, not the matcher labels; also remove the dead
AspNetCoreRequestDelegate surface entry since the RequestDelegate.Invoke
MethodKind guard makes it non-matchable. Keep the receiver surface explicit and
aligned with each InterceptorTarget.ReceiverType so
GetInterceptorReceiverSurface reflects current-tree behavior only.

Source: Coding guidelines

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Verified each sub-claim against the matchers; split verdict, fixed in the latest push:

Accepted — RequestDelegate row is dead (root-fixed harder than suggested): TryGetAspNetCoreRequestDelegateInvocation requires MethodKind.Ordinary, but a delegate's Invoke is always MethodKind.DelegateInvoke — unmatchable by construction since 770a897. Rather than just dropping it from the export, the whole path is deleted (matcher row, emission row, detection method, enum member). Server spans still come from the IStartupFilter middleware; aspnetcore-middleware-delegate-ok + real-aspnetcore-demo-ok confirm.

Accepted — Redis: matcher gates IDatabaseAsync; the surface now says StackExchange.Redis.IDatabaseAsync.

Rejected — EndpointRouteBuilderExtensions / LoggerExtensions are not 'matcher labels': those matchers literally gate on the extension class as the containing type (Detection.cs: IsType(original.ContainingType, "...EndpointRouteBuilderExtensions")), same convention as the third extension-method matcher (EFCore EntityFrameworkQueryableExtensions). The column documents the matched API surface, which for extension methods is the defining extensions class; collapsing LoggerExtensions to ILogger would duplicate the existing ILogger row and lose the direct-call vs extension-call distinction.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e
fd -e cs QylAutoInstrumentationGenerator.Detection.cs

echo "---LoggerExtension---"
rg -n -B3 -A15 'TryGetLoggerExtensionInvocation' src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Detection.cs

echo "---EndpointMap---"
rg -n -B3 -A20 'TryGetAspNetCoreEndpointMapInvocation' src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Detection.cs

echo "---EFCoreQueryable---"
rg -n -B3 -A15 'TryGetEntityFrameworkCoreQueryableInvocation' src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Detection.cs

echo "---RequestDelegate removal check---"
rg -n 'RequestDelegate' src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/*.cs

echo "---Redis surface check---"
rg -n 'StackExchangeRedis' src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/*.cs

Length of output: 5351


@ANcpLua Confirmed against source, no further pushback:

  • RequestDelegate: verified only remaining reference is line 202 (internal disambiguation inside TryGetAspNetCoreEndpointMapInvocation, not a standalone matcher/emission/enum path). Dead path is fully gone.
  • Redis: QylGeneratedSourceInterceptorCatalog.cs line 22 now reads StackExchange.Redis.IDatabaseAsync, matching the gate in TryGetStackExchangeRedisInvocation.
  • EndpointRouteBuilderExtensions / LoggerExtensions: withdrawn. TryGetEntityFrameworkCoreQueryableInvocation (Detection.cs line 1201) gates on EntityFrameworkQueryableExtensions the same way, and that row was never in question — so the extension-class-as-matched-surface convention is pre-existing and applied uniformly across all three extension-method matchers, not something introduced here. Collapsing LoggerExtensions into ILogger would in fact break that convention and merge two distinct matcher identities into one row. My original claim was wrong on this point.

✏️ Learnings added
Learnt from: ANcpLua
Repo: ANcpLua/Qyl.OpenTelemetry.AutoInstrumentation PR: 28
File: src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylGeneratedSourceInterceptorCatalog.cs:0-0
Timestamp: 2026-07-01T23:14:59.040Z
Learning: In src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.Detection.cs, extension-method matchers (e.g. TryGetAspNetCoreEndpointMapInvocation gating on EndpointRouteBuilderExtensions, TryGetLoggerExtensionInvocation gating on LoggerExtensions, TryGetEntityFrameworkCoreQueryableInvocation gating on EntityFrameworkQueryableExtensions) intentionally publish the extension class as the matcher's receiver-type/surface identity in QylGeneratedSourceInterceptorCatalog.cs, even though the resulting InterceptorTarget.ReceiverType at runtime may be the actual instance interface (e.g. IEndpointRouteBuilder). This is an established, uniformly-applied convention distinguishing extension-call matchers from direct-instance-call matchers (e.g. the separate ILogger row) — it is not a defect to be "fixed" by collapsing to the instance type.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

ANcpLua and others added 4 commits July 2, 2026 01:14
…e interceptor path; truthful Redis receiver surface

TryGetAspNetCoreRequestDelegateInvocation required MethodKind.Ordinary on
RequestDelegate.Invoke — but a delegate's Invoke is always
MethodKind.DelegateInvoke, so the matcher has been unmatchable by
construction since 770a897 added the guard. Server-span coverage comes
from the IStartupFilter middleware (d31d94f); the generator path was dead
weight advertised as live surface in the TCG. Deleted: matcher row,
emission row, detection method, enum member. The runtime
QylInterceptedAspNetCore.InvokeAsync helper stays (startup filter uses it).

Redis: the matcher gates StackExchange.Redis.IDatabaseAsync; publish that
instead of the narrower IDatabase.

Verified: build 0/0, generator-snapshots-ok (byte-identical),
contract-invariants-ok, aspnetcore-middleware-delegate-ok,
tcg-publishing-demo-ok, real-aspnetcore-demo-ok.

Reported-by: coderabbitai (receiver-surface truthfulness)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Build()-interceptor/IStartupFilter rewire (PR #20) is merged, released
(v4.0.x on the feed), and pinned by qyl at 4.0.0; its two follow-up gaps
were closed by later commits (route backfill after next() in
QylInterceptedAspNetCore.RecordResponse, single-owner-per-signal registry
in PR #23). The descriptor-metadata root fix is this PR — documented by
the PR itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…span combination docs

- AGENTS.md deleted, CLAUDE.md is now the real file (was a symlink) — this
  repo's agent rules are consumed via CLAUDE.md only
- rules updated to current tree: structural descriptor model + invariants
  routed to verify-contract-invariants.py; no-callsite-arbitration invariant
  (the deleted Build()-interceptor coordination protocol stays deleted);
  publish vs build version roles stated per version-sync; note on not racing
  local verifier runs against CI on the shared self-hosted hosts
- AddQylAspNetCoreInstrumentation: remark predated the single-owner signal
  registry (PR #23) — combining with .Hosting is safe (listener lane defers),
  say so; document IStartupFilter registration-order contract (server span
  stays outermost)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ANcpLua
ANcpLua merged commit 13bcb38 into main Jul 2, 2026
5 of 9 checks passed
@ANcpLua
ANcpLua deleted the refactor/descriptor-metadata-root-fix branch July 2, 2026 00:54
ANcpLua added a commit that referenced this pull request Jul 18, 2026
… left unstructural

High-effort review of 13bcb38 (which subsumed fix/dbcontract-default-throw-and-dead-field
via squash) confirmed the deleted self-referential validators left six invariants with no
structural or gate replacement. This makes each one structural or gate-enforced:

- TraceInterceptorBodyDescriptor.RuntimeHelper is now a required constructor parameter
  (was optional-with-default but dereferenced unconditionally — an omitted helper emitted
  syntactically broken code into consumer compilations); drop the dead IsDefined column
  from TraceRuntimeHelperDescriptor.
- Logger level mapping is single-sourced: detection and emission share
  GetLoggerExtensionLevelName, and an unknown method name now throws at generation
  instead of silently forwarding at LogLevel.None (records dropped invisibly).
- Delete the never-constructed LoggerInterceptorBodyKind.None member.
- Deduplicate GetDbSystemName into QylDbActivityPolicy (the QylDbClientMetrics copy was
  a truncated fork), enumerate ADONET explicitly, and throw on unknown ids per the
  codebase-wide unknown->throw invariant.
- verify-contract-invariants: new verify_matcher_registration gate (an unregistered or
  orphaned TryGet*Invocation detection method previously passed every gate — mutation-
  verified silent telemetry loss); derive the emitter-dispatch case list structurally
  from declared BodyDescriptor types instead of a hand-pinned 8-token mirror (a 9th
  body type without a case now fails the gate); make emission-row parsing wrap-tolerant;
  cache read_generator_sources.
- verify-aot-publish-gate: approve runtime-pack vendor warnings at 10.0.10 (SDK patch
  moved packs from 10.0.9; same diagnostics, fail-closed drift check fired as designed).

Both new gate checks mutation-tested (deleted matcher row and phantom body type each go
red). Full handoff gate green: aot-autoinstrumentation-goal-ok.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

2 participants