Skip to content

[codex] Verify ProjectReference autoinstrumentation assets - #1

Merged
ANcpLua merged 20 commits into
mainfrom
claude/projectreference-build-assets
Jun 6, 2026
Merged

[codex] Verify ProjectReference autoinstrumentation assets#1
ANcpLua merged 20 commits into
mainfrom
claude/projectreference-build-assets

Conversation

@ANcpLua

@ANcpLua ANcpLua commented Jun 5, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add guarded build/ package assets alongside buildTransitive/ so direct package layout is explicit without double-compiling the intercepts attribute.
  • Add a ProjectReference dogfooding verifier that proves managed execution and NativeAOT publish with explicit analyzer/build-asset wiring.
  • Fix the options static-initialization risk by removing the explicit cctor, declaring instrumentation id arrays before CurrentHolder, and adding a null guard at the signal instrumentation boundary.
  • Precompute configured captured header/metadata tag names at options-load time and use those maps in HttpClient, ASP.NET Core, gRPC, and HttpWebRequest hot paths.
  • Gate the semconv conformance counter behind QYL_CONFORMANCE_ENABLED=1 or AddQylAutoInstrumentation(o => o.EnableConformanceProcessor = true) and verify default-off/env-on/hosting-on behavior.
  • Add tools/smoketest.sh plus a GitHub Actions smoke workflow that builds PackageReference and ProjectReference consumers, runs both under JIT and NativeAOT, asserts generated interceptor output, and diffs deterministic stdout against a golden fixture.
  • Add a dedicated AOT warning workflow; the smoke publish path now captures NativeAOT publish output and fails on any IL2xxx, IL3xxx, IL4xxx, or CA warning for PackageReference and ProjectReference consumers.
  • Add Microsoft.CodeAnalysis.PublicApiAnalyzers for the five runtime/package projects, commit shipped public API baselines, and verify the baseline files in the combined goal gate.
  • Add docs/rfc/0001-interceptor-substrate.md as the upstream/contribution artifact for the AOT-native [InterceptsLocation] substrate, plus tools/verify-rfc-artifact.py so the RFC minimum contract is part of the goal gate.
  • Add committed source-generator snapshots for the fixed ILogger fixture and tools/verify-generator-snapshots.py; the combined goal gate now fails on generated interceptor or generated contract drift.
  • Add docs/coverage-matrix.md, generated from the 60-item source-of-truth YAML plus the current generator/runtime bindings, and make tools/verify-contract-coverage-report.py fail if the matrix is stale.
  • Add tools/verify-webapi-aot-demo.py plus qyl-webapi-aot-demo CI. It packs the current NuGets, creates a .NET 10 minimal Web API with ASP.NET Core, HttpClient, EFCore Sqlite compiled model, and SqlClient error-path instrumentation, publishes NativeAOT, runs the binary, and compares canonical telemetry output against a golden JSON file while failing on qyl-owned AOT analyzer warnings.
  • Add tools/verify-otlp-golden-fixtures.py plus tools/Qyl.AutoInstrumentation.OtlpGoldenFixtures/golden/webapi-aot-traces.otlp.json; it maps the NativeAOT WebAPI proof signals into deterministic OTLP-shaped resourceSpans/scopeSpans/spans JSON and fails on output drift.
  • Add source-generator XML-doc enforcement: Qyl.AutoInstrumentation.SourceGenerators now generates XML docs, treats CS1591 as an error, documents the interceptor generator, and tools/verify-xml-doc-enforcement.py verifies the gate.

Validation

  • bash tools/smoketest.sh
  • python3 tools/verify-public-api-baseline.py && dotnet build Qyl.AutoInstrumentation.slnx -c Release -v quiet
  • dotnet build Qyl.AutoInstrumentation.slnx -c Release -v quiet && python3 tools/verify-environment-options-behavior.py && python3 tools/verify-conformance-opt-in.py && python3 tools/verify-source-interceptor-consumer.py
  • python3 tools/verify-rfc-artifact.py && python3 tools/verify-aot-autoinstrumentation-goal.py
  • python3 tools/verify-generator-snapshots.py
  • python3 tools/verify-contract-coverage-report.py
  • python3 tools/verify-webapi-aot-demo.py
  • python3 tools/verify-otlp-golden-fixtures.py
  • python3 tools/verify-xml-doc-enforcement.py

Known notes

  • Bare runtime ProjectReference remains intentionally documented as unsupported for zero-code interception because MSBuild resolves it as a reference assembly, not analyzer/build assets. The supported source-tree path is explicit runtime ProjectReference + analyzer/build-asset wiring.
  • The Web API demo treats third-party EFCore/SqlClient NativeAOT warnings as an app/dependency boundary and fails only on qyl-owned AOT analyzer warnings.
  • The OTLP fixture is canonical OTLP-shaped JSON derived from the NativeAOT WebAPI proof, not a collector-backed OTLP transport test yet.
  • Core runtime XML-doc enforcement is not complete yet: a measured CS1591 build on Qyl.AutoInstrumentation.csproj currently reports 343 missing public-doc entries, mostly generated-interceptor runtime bridge methods/constants.
  • Full BenchmarkDotNet hot-path reports, collector-backed OTLP transport fixtures, full runtime XML-doc enforcement, release tag, and docs/score-100-justification.md remain outside this PR so far.

ANcpLua and others added 20 commits June 5, 2026 17:57
A triage (reading the actual code, not file names) found the branch is mostly
real: the feature code is correct and hallucination-free, 14/19 verifiers are
genuine behavioral checks (NativeAOT publish + golden diff, real OTLP-protobuf
wire round-trip, IL2xxx/IL3xxx publish-warning gates), and the golden fixtures
are produced by real harnesses, not hand-authored.

Remove only the genuine ceremony:
- docs/score-100-justification.md + tools/verify-score-100-justification.py —
  self-assigned "100/100" prose graded by a token-grep + 900-word floor; its
  benchmark numbers even contradict the committed reports it cites.
- tools/verify-rfc-artifact.py — token-greps the RFC markdown for literal
  phrases (and a hardcoded date that already drifted). The RFC doc itself stays.
- tools/verify-benchmark-report.py + tools/run-hotpath-benchmarks.sh +
  docs/benchmarks/*-report-github.md — token-grep over committed, hardware-pinned
  BenchmarkDotNet result exports that are never re-measured. The benchmark
  PROJECT under benchmarks/ stays.
- .github/workflows/aot-warning-gate.yml — byte-identical to smoketest.yml
  (both run `bash tools/smoketest.sh`); keep one.

Also trim the 3 deleted scripts out of verify-aot-autoinstrumentation-goal.py's
COMMANDS chain and drop the dangling score-doc / verify-rfc-artifact references
from the RFC. Build stays green (0/0); goal-gate still references only existing
scripts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ANcpLua
ANcpLua marked this pull request as ready for review June 6, 2026 10:07
@ANcpLua

ANcpLua commented Jun 6, 2026

Copy link
Copy Markdown
Owner Author

@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: 9a4c2f37bd

ℹ️ 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".

private static object ToTagValue(IEnumerable<string> values)
{
if (values is string[] array)
return array.Length is 1 ? array[0] : array;

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 captured header tags as arrays

For configured HTTP headers with exactly one value, this now returns a scalar string instead of the previous values.ToArray() representation. The OpenTelemetry HTTP semantic conventions define http.request.header.<key> / http.response.header.<key> as string[], including single-value headers (spec), so OTLP output and backends that validate array-typed captured headers will regress whenever a configured header has only one value. Keep emitting a one-element array here, and apply the same fix to the other new single-value header/metadata capture paths.

Useful? React with 👍 / 👎.

Comment on lines +32 to +33
if (!IsEnabled())
return;

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 Avoid sampling spans when conformance is off

With the default QYL_CONFORMANCE_ENABLED unset, this opt-out happens only after QylInstrumentation.Activate() has already registered an ActivityListener that samples AllDataAndRecorded. In a hosted app with no external qyl listener, that internal listener still makes QylActivitySource.HasListeners() true, so every intercepted trace/log path allocates and populates Activities only to return here; this keeps the production hot-path cost that this change is trying to disable. Gate listener registration on the same conformance opt-in instead of returning after span creation.

Useful? React with 👍 / 👎.

Comment thread tools/smoketest.sh
local consumer="$2"
local matches

matches="$(grep -Eo '\\b(IL2[0-9]{3}|IL3[0-9]{3}|IL4[0-9]{3}|CA[0-9]{4})\\b' "$log" | sort -u || true)"

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 Fix the AOT warning grep pattern

In this shell string the doubled backslashes are passed to grep -E as \\b, so the pattern looks for a literal backslash plus b rather than a word boundary; for example, printf 'IL3000\n' | grep -Eo '\\b(IL3[0-9]{3})\\b' produces no match. As a result, this smoke gate reports warnings=0 even when the publish log contains IL2xxx/IL3xxx/IL4xxx/CA warnings that the workflow is supposed to fail on.

Useful? React with 👍 / 👎.

@ANcpLua
ANcpLua merged commit 8a92ba4 into main Jun 6, 2026
13 checks passed
@ANcpLua
ANcpLua deleted the claude/projectreference-build-assets branch June 6, 2026 10:30
ANcpLua added a commit that referenced this pull request Jun 11, 2026
…sets

The slim-history rebuild of main (PR #2) orphaned the original working
branch, which then received nine more commits (2026-06-09 → 06-11 04:02)
that never reached the new main: HttpClient effective-request metadata
resolution, header-capture fallback path (new QylCaptureHelpers),
status-handling tightening, verifier simplification into shared
verify_helpers.py, and the extracted WebApiAotDemo Program.cs that the
rebuild had dropped while keeping its golden report.

Three-way merged the two genuinely diverged files: ModuleInitializerBoot
(night listener-table refactor + main's sober XML docs) and the source
generator (night dispatch-table refactor + main's ANcpLua.Roslyn.Utilities
adoption).

Skipped docs/pr1-remaining.md — completed-checklist evidence for the
already-merged PR #1; its tracked changes land with this commit.

Also filed the drafted NServiceBus AOT upstream issue under
docs/upstream/ instead of leaving it untracked at the repo root.

Verified: dotnet build (0 warnings), verify-generator-snapshots,
verify-public-api-baseline, verify-source-interceptor-consumer all ok.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ANcpLua added a commit that referenced this pull request Jul 1, 2026
…trument (CODE RED #1/#2/#10) (#22)

An adversarial OTel-compliance + honesty audit rated this code 3/10. This lands the
verified subset of the confirmed HIGH/MEDIUM defects.

#1 (HIGH, ~0-duration lie): the HttpClient/AspNetCore/gRPC DiagnosticListeners created
their span on the framework's *.Stop event via the 2-arg StartActivity (StartTimeUtc=now)
and immediately disposed it, so every emitted span had ~0 duration instead of the real
operation latency. New internal QylActivitySource.StartAtAmbientStart(name, kind) stamps
the span to the ambient framework Activity's real StartTimeUtc (parented to it for trace
correlation), with a now-stamped fallback when there is no ambient activity. All three
listeners switched to it.

#2 (HIGH, dishonest doc): DiagnosticListenerSubscriber claimed it "publishes the same span
shapes" — false while durations were fabricated. Doc now states the actual mechanism
(reacts on *.Stop, stamps to ambient start for real duration).

#10 (MEDIUM, metrics-semconv): dotnet.thread_pool.thread.count was an unitless
ObservableGauge<int>; per OTel semconv it is an (Observable)UpDownCounter with UCUM unit
{thread}. Switched instrument type + added unit.

Verified: core.slnf Release 0/0; verify-webapi-aot-demo passes (fixture unchanged — the
attribute shape is identical; note the fixture is duration-insensitive, which is itself
tracked as a separate finding). No PublicAPI change (all internal/private).

NOT in this commit (separate verified increments): #3 double-count between the listener
and interceptor lanes (+ the fixture that masks it), #4/#11 OTLP Events/Links/Status.Message
dropped on ingest, #6 url.scheme, #7 http.request.method_original on the interceptor path,
#9 Azure span name ignores methodName.

Co-authored-by: Claude Opus 4.8 (1M context) <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.

1 participant