Skip to content

chore(api): check the packaged API against the release on nuget.org, not just against a file in the PR - #705

Merged
tylerkron merged 3 commits into
mainfrom
chore/636-package-validation
Aug 30, 2026
Merged

tylerkron merged 3 commits into
mainfrom
chore/636-package-validation

Conversation

@tylerkron

@tylerkron tylerkron commented Aug 29, 2026 •

Copy link
Copy Markdown
Contributor

What was wrong

ADR 0002 promises source compatibility for Daqifi.Core's public API. PR #680 made that promise visible: the surface is checked in as PublicAPI.Shipped.txt, and RS0016/RS0017 fail the build whenever the code and that file disagree.

But both files travel in the PR. Delete a public member and its line in PublicAPI.Shipped.txt in the same change and the build is green — which is precisely the shape of a break ADR 0002 is meant to catch. A deliberate break and an accidental one look identical to the build, and the accidental one reaches a consumer (daqifi-desktop, Daqifi.Mcp, an external integrator) on their next restore.

I confirmed this on main before changing anything: narrowing DeviceMetadata.IpAddress's setter to internal and deleting its one line from PublicAPI.Shipped.txt builds clean, 0 warnings, 0 errors.

How it's fixed

Compare against something the PR cannot edit: the package already on nuget.org.

Daqifi.Core now sets EnablePackageValidation with PackageValidationBaselineVersion pinned to 1.7.0, the last published version, and CI packs the library so ApiCompat compares the packaged assemblies — both net9.0 and net10.0 — against that download. The same removal above now fails with CP0002 on both frameworks.

That leaves the source analyzer as the fast, precise, everyday check and package validation as the backstop that answers "does this still work for someone who already depends on 1.7.0?"

A second CI step keeps the baseline honest: it resolves the newest stable version from the nuget.org index and fails when the pinned baseline is behind it. Without that, the check quietly narrows over time — ApiCompat can only report a member the baseline package actually contains, so everything added since the pinned version would fall outside the comparison entirely.

Things a reviewer may want to push back on

CP0003 is suppressed. It compares assembly versions, and this repo carries none — the version is stamped only by release.yml from the git tag. Every other build ships the SDK's 1.0.0 placeholder, so CP0003 reports "lower than the 1.7.0 baseline" on every build, including ones that change no API at all. Suppressing it keeps the rules that carry signal here (CP0002, CP1002) readable. Version ordering is decided upstream by which tag the release is cut from.

The gate is in ci.yml, not release.yml. Package validation only runs inside dotnet pack, and --no-build skips the ApiCompat targets entirely — verified: with --no-build the pack is silent on a break that otherwise produces CP0002. release.yml packs --no-build on purpose, so its assemblies keep the version stamped by the earlier build step; removing that flag would republish the assemblies as 1.0.0.0. So the PR/main gate is the right place, and everything reaching a release tag has passed it. PublicApiTrackingTests asserts the CI pack step never grows a --no-build.

The baseline-freshness check is a failure, not a warning. After a release is published, CI goes red on every PR until the baseline bump lands. That is the intent — the bump PR itself is green, and the alternative is a check that silently covers less over time. The bump belongs in the same change that moves Unshipped entries into Shipped.

Prereleases are excluded when deciding what is "newest". release.yml accepts prerelease tags and the index lists them, but a prerelease is not what a consumer restores by default, so it must not become the version every other PR is required to match.

It costs one extra compile. dotnet pack defaults to Release, so the step rebuilds Daqifi.Core rather than reusing the Debug output. One project, on the ubuntu leg only — the packaged shape is identical on all three. Measured at ~6s in CI.

Verification

  • Mutation proof for package validation: setter narrowed to internal and its PublicAPI.Shipped.txt line deleted → dotnet build green, dotnet pack red with CP0002 on net9.0 and net10.0.
  • The pre-existing analyzer gate re-verified in both directions: a new public type → RS0016; a narrowed setter → RS0017.
  • Baseline-freshness check: pinning 1.6.0 against the published 1.7.0 exits 1 with the ::error:: annotation. Prerelease filter checked against a simulated index and the live one.
  • Every new test checked to fail when its wiring is removed.
  • Full suite green on net9.0 + net10.0: 4152 + 217 per framework, 0 failed. CI green on all three OS legs, with both new steps passing.

No bench run — this is a build-configuration change with no device path.

Closes #636

Not merging — for review.

🤖 Generated with Claude Code

…not just against a file in the PR

PR #680 checked the public surface into PublicAPI.Shipped.txt and let
RS0016/RS0017 fail the build when the code and that file disagree. Both files
are part of the PR, though, so removing a public member and its entry in one
change still compiles green - which is the exact case ADR 0002 cares about.

Turn on EnablePackageValidation with the baseline pinned to 1.7.0, the last
version on nuget.org, and pack the library in CI so ApiCompat compares the
packaged assemblies for both target frameworks against that published package.
A PR cannot edit the baseline.

CP0003 is suppressed: it compares assembly versions, and the version is stamped
only by release.yml from the git tag, so every other build carries the SDK's
1.0.0 placeholder and CP0003 fires on changes that touch no API at all.

The CI step deliberately omits --no-build, which skips the ApiCompat targets
outright and makes the pack validate nothing; a test asserts it stays absent.

Closes #636

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tylerkron
tylerkron requested a review from a team as a code owner August 29, 2026 14:25
@tylerkron

Copy link
Copy Markdown
Contributor Author

/agentic_review

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Validate packaged API against the latest NuGet release

⚙️ Configuration changes 🧪 Tests 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Validate packaged Daqifi.Core assemblies against NuGet version 1.7.0 during Ubuntu CI.
• Preserve source and binary compatibility checks while suppressing irrelevant assembly-version
 diagnostics.
• Test and document validation wiring, baseline updates, and intentional-break suppressions.
Diagram

graph TD
  CI["Ubuntu CI"] --> Pack["dotnet pack"] --> Config["Validation config"] --> Compat["ApiCompat"] --> Gate["Compatibility gate"]
  NuGet[("NuGet 1.7.0")] --> Compat
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Validate only during release
  • ➕ Avoids an extra compile in pull-request CI.
  • ➕ Runs against release-stamped assembly versions.
  • ➖ Detects compatibility breaks after normal review gates.
  • ➖ The current release pack uses --no-build, which skips ApiCompat targets.
2. Compare only checked-in API files
  • ➕ Keeps validation fast during ordinary builds.
  • ➕ Provides precise source-level API diffs.
  • ➖ A pull request can remove both a member and its baseline entry.
  • ➖ Does not verify the binary shape of each packaged target framework.

Recommendation: Keep the proposed layered approach: source analyzers provide fast feedback, while an Ubuntu-only pack compares immutable published binaries before merge. Release-only validation is too late and incompatible with the required --no-build packaging flow; checked-in API files alone cannot independently detect coordinated removals.

Files changed (4) +131 / -2

Tests (1) +60 / -2
PublicApiTrackingTests.csGuard package-validation and CI wiring +60/-2

Guard package-validation and CI wiring

• Adds tests requiring package validation to remain enabled with a parseable baseline version. Also verifies CI contains exactly one Daqifi.Core pack command and that it does not disable builds, which would silently skip ApiCompat.

src/Daqifi.Core.Tests/Build/PublicApiTrackingTests.cs

Documentation (1) +24 / -0
CONTRIBUTING.mdDocument published-package API validation +24/-0

Document published-package API validation

• Explains local validation, baseline updates after releases, and why --no-build must not be used. Documents the suppression-file workflow required to make intentional binary breaks explicit during review.

CONTRIBUTING.md

Other (2) +47 / -0
ci.ymlAdd an Ubuntu-only packaged API compatibility gate +23/-0

Add an Ubuntu-only packaged API compatibility gate

• Adds a CI pack step for Daqifi.Core that intentionally rebuilds the project so ApiCompat targets execute. The generated package is discarded after validation, avoiding duplicate work on the Windows and macOS matrix legs.

.github/workflows/ci.yml

Daqifi.Core.csprojEnable package validation against Daqifi.Core 1.7.0 +24/-0

Enable package validation against Daqifi.Core 1.7.0

• Enables SDK package validation and pins the published 1.7.0 package as the baseline for all target frameworks. Suppresses CP0003 because non-release builds use the SDK's placeholder assembly version, while retaining API and target-framework compatibility diagnostics.

src/Daqifi.Core/Daqifi.Core.csproj

@qodo-code-review

qodo-code-review Bot commented Aug 29, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Prerelease baseline cannot pass ✓ Resolved 🐞 Bug ≡ Correctness ⭐ New
Description
The latest-version query treats a newly published prerelease as the required baseline, but
CoreProject_PinsThePackageValidationBaselineToAPublishedVersion rejects SemVer prerelease strings
through Version.TryParse. Because the release workflow explicitly publishes versions such as
1.0.0-beta.1, the next CI run cannot pass whether the baseline remains stable or is correctly
bumped to that prerelease.
Code

.github/workflows/ci.yml[R94-96]

+        latest=$(curl -sSf https://api.nuget.org/v3-flatcontainer/daqifi.core/index.json | jq -r '.versions[-1]')
+        echo "baseline=$baseline latest=$latest"
+        if [ "$baseline" != "$latest" ]; then
Relevance

●●● Strong

Prerelease releases are explicitly supported, so verbatim latest-version comparison conflicts with
Version.TryParse baseline validation.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The release workflow accepts prerelease tags, packages with that exact version, and pushes them; the
new CI logic requires the final NuGet index version verbatim, while the baseline unit test uses
System.Version parsing rather than NuGet/SemVer parsing.

.github/workflows/release.yml[31-35]
.github/workflows/release.yml[47-57]
.github/workflows/ci.yml[92-100]
src/Daqifi.Core.Tests/Build/PublicApiTrackingTests.cs[118-134]
CONTRIBUTING.md[93-98]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Publishing an allowed prerelease makes CI require a prerelease baseline, while the project test rejects that valid NuGet version.

## Issue Context
The release workflow accepts and publishes hyphenated SemVer prereleases. Decide whether package validation should track all published versions or stable releases only, then make both the NuGet query and baseline validation follow that policy.

## Fix Focus Areas
- .github/workflows/ci.yml[92-96]
- src/Daqifi.Core.Tests/Build/PublicApiTrackingTests.cs[118-134]
- .github/workflows/release.yml[31-35]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. CI guard tests only strings ✓ Resolved 🐞 Bug ⚙ Maintainability ⭐ New
Description
Ci_FailsWhenTheBaselineHasFallenBehindTheLatestRelease only checks that the property name and
NuGet URL occur somewhere in the workflow, so it remains green if the comparison or exit 1 is
removed and CI no longer fails on drift. This defeats the test's stated purpose of making silent
disablement of the baseline-currentness gate detectable.
Code

src/Daqifi.Core.Tests/Build/PublicApiTrackingTests.cs[R145-152]

+        Assert.Contains(
+            "PackageValidationBaselineVersion",
+            CiWorkflowText,
+            StringComparison.Ordinal);
+        Assert.Contains(
+            "api.nuget.org/v3-flatcontainer/daqifi.core/index.json",
+            CiWorkflowText,
+            StringComparison.Ordinal);
Relevance

●●● Strong

Recent accepted precedent strengthens weak structural guards when they fail to detect API or
configuration drift.

PR-#680

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The test contains only two Assert.Contains calls, while the actual enforcement depends on separate
comparison and failure commands that it never inspects.

src/Daqifi.Core.Tests/Build/PublicApiTrackingTests.cs[137-153]
.github/workflows/ci.yml[88-101]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The regression test verifies two unrelated substrings but not that CI compares the values and exits unsuccessfully when they differ.

## Issue Context
The workflow currently implements the gate correctly, but deleting or neutralizing its `if`/`exit 1` logic leaves this test green. Extract the check into a testable script or inspect the relevant workflow step closely enough to prove it performs the comparison and failure.

## Fix Focus Areas
- src/Daqifi.Core.Tests/Build/PublicApiTrackingTests.cs[137-153]
- .github/workflows/ci.yml[88-101]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Stale baseline misses newer APIs ✓ Resolved 🐞 Bug ☼ Reliability
Description
CoreProject_PinsThePackageValidationBaselineToAPublishedVersion accepts any parseable version, so
it remains green if the baseline is never advanced after a release. APIs first shipped after that
stale baseline are absent from the ApiCompat comparison; deleting one together with its
PublicAPI.Shipped.txt entry would then pass both guards.
Code

src/Daqifi.Core.Tests/Build/PublicApiTrackingTests.cs[R125-126]

+        Assert.True(
+            baseline is not null && Version.TryParse(baseline, out _),
Relevance

●●● Strong

Recent PublicAPI guard feedback was accepted; this directly strengthens release-baseline reliability
and prevents stale comparisons.

PR-#680

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The test checks only syntactic version validity, while the documented release process says Unshipped
APIs become Shipped at release time and the current Unshipped file contains many APIs absent from
the 1.7.0 package. Therefore, after such APIs are released, leaving the baseline at 1.7.0 means
ApiCompat has no old member to report if one of them is later removed.

src/Daqifi.Core.Tests/Build/PublicApiTrackingTests.cs[114-129]
CONTRIBUTING.md[55-57]
CONTRIBUTING.md[70-70]
CONTRIBUTING.md[93-95]
src/Daqifi.Core/PublicAPI.Unshipped.txt[2-10]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The package-validation test accepts any parseable baseline version, allowing the baseline to remain stale and miss removals of APIs introduced in newer releases.

## Issue Context
An older compatibility baseline is not stricter for APIs that did not exist in that version. The repository already distinguishes APIs added since the baseline in `PublicAPI.Unshipped.txt`, and the contributor documentation currently makes the opposite claim.

## Fix Focus Areas
- src/Daqifi.Core.Tests/Build/PublicApiTrackingTests.cs[114-129]
- CONTRIBUTING.md[93-95]
- src/Daqifi.Core/Daqifi.Core.csproj[28-29]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
Review mode: ⚖️ Balanced: The push changes a CI enforcement gate and its validation tests, with subtle shell/JSON filtering and failure-path behavior that merits a careful single-pass review; it is not broad or dense enough for extended.

Grey Divider

Tip of the day
💡 Did you know, you can enable the Remediation agent and Qodo fixes findings in a dedicated fix PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Previous reviews

Review updated until commit 5d31439

Results up to commit ddab485 ⚖️ Balanced


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Remediation recommended
1. Stale baseline misses newer APIs ✓ Resolved 🐞 Bug ☼ Reliability
Description
CoreProject_PinsThePackageValidationBaselineToAPublishedVersion accepts any parseable version, so
it remains green if the baseline is never advanced after a release. APIs first shipped after that
stale baseline are absent from the ApiCompat comparison; deleting one together with its
PublicAPI.Shipped.txt entry would then pass both guards.
Code

src/Daqifi.Core.Tests/Build/PublicApiTrackingTests.cs[R125-126]

+        Assert.True(
+            baseline is not null && Version.TryParse(baseline, out _),
Relevance

●●● Strong

Recent PublicAPI guard feedback was accepted; this directly strengthens release-baseline reliability
and prevents stale comparisons.

PR-#680

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The test checks only syntactic version validity, while the documented release process says Unshipped
APIs become Shipped at release time and the current Unshipped file contains many APIs absent from
the 1.7.0 package. Therefore, after such APIs are released, leaving the baseline at 1.7.0 means
ApiCompat has no old member to report if one of them is later removed.

src/Daqifi.Core.Tests/Build/PublicApiTrackingTests.cs[114-129]
CONTRIBUTING.md[55-57]
CONTRIBUTING.md[70-70]
CONTRIBUTING.md[93-95]
src/Daqifi.Core/PublicAPI.Unshipped.txt[2-10]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The package-validation test accepts any parseable baseline version, allowing the baseline to remain stale and miss removals of APIs introduced in newer releases.

## Issue Context
An older compatibility baseline is not stricter for APIs that did not exist in that version. The repository already distinguishes APIs added since the baseline in `PublicAPI.Unshipped.txt`, and the contributor documentation currently makes the opposite claim.

## Fix Focus Areas
- src/Daqifi.Core.Tests/Build/PublicApiTrackingTests.cs[114-129]
- CONTRIBUTING.md[93-95]
- src/Daqifi.Core/Daqifi.Core.csproj[28-29]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread src/Daqifi.Core.Tests/Build/PublicApiTrackingTests.cs
…est release

Qodo review, round 1. The claim that an out-of-date baseline "only makes the
check stricter" was wrong, and it was repeated in the csproj comment and in
CONTRIBUTING.md. ApiCompat can only report a member the baseline package
actually contains, so an old baseline is a *narrower* check: everything added
since the pinned version sits outside the comparison and could be removed -
together with its PublicAPI.Shipped.txt entry - with both guards still green.

Correct the claim in both places, and add a CI step that resolves the newest
published version from the nuget.org index and fails when the pinned baseline
is behind it, so forgetting the bump after a release is loud rather than
silent. Verified by pinning 1.6.0 against the published 1.7.0: exit 1 with the
::error:: annotation.

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

Copy link
Copy Markdown
Contributor Author

Round 1 finding accepted — it was a genuine logic error, not just a wording problem.

I had claimed in two places (the Daqifi.Core.csproj comment and CONTRIBUTING.md) that leaving PackageValidationBaselineVersion behind "only makes the check stricter." That is wrong for exactly the reason given: ApiCompat can only report a member the baseline package actually contains, so an old baseline is a narrower check. Every API added after the pinned version is outside the comparison, and could be removed together with its PublicAPI.Shipped.txt entry with both guards still green — reopening the same loophole this PR exists to close.

Fixed in 526b554:

  • Corrected the claim in the csproj comment and in CONTRIBUTING.md; the bump is now stated as required, with the narrower-not-stricter reasoning spelled out.
  • Added a CI step that resolves the newest published version from the nuget.org flat-container index and fails when the pinned baseline is behind it. Nothing in the repo records which version is current, so the question has to be asked of nuget.org — and the pack step already downloads from that same index, so it is not a new dependency.
  • The test keeps the parseability assertion (that the baseline is pinned at all) and gained Ci_FailsWhenTheBaselineHasFallenBehindTheLatestRelease, which asserts CI still asks that question.

Verified by pinning the baseline to 1.6.0 against the published 1.7.0: the step exits 1 with the ::error:: annotation naming the drift. Restored to 1.7.0, which passes.

One deliberate consequence worth flagging: this is a failure, not a warning, so CI goes red on every PR between a release being published and the baseline bump landing. That is the intent — the bump PR itself is green, and the alternative is a check that quietly covers less over time.

Full suite green on net9.0 + net10.0 after the change: 4152 + 217 per framework, 0 failed.

@tylerkron

Copy link
Copy Markdown
Contributor Author

/agentic_review

Comment thread .github/workflows/ci.yml Outdated
Comment thread src/Daqifi.Core.Tests/Build/PublicApiTrackingTests.cs Outdated
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 526b554

…rd test bite

Qodo review, round 2. Two findings, both valid.

A published prerelease would have deadlocked the two halves of this change
against each other. release.yml accepts prerelease tags, the flat-container
index lists them, and the freshness check took the last entry verbatim - so
publishing 1.8.0-beta.1 would have demanded a baseline that
CoreProject_PinsThePackageValidationBaselineToAPublishedVersion rejects, since
Version.TryParse does not accept SemVer prerelease strings. Neither keeping the
baseline nor bumping it could pass. The query now filters prereleases out: the
baseline is always the newest stable release, which is what a consumer restores
by default, and which is parseable. Also guard an empty result.

Ci_FailsWhenTheBaselineHasFallenBehindTheLatestRelease asserted only that two
strings appeared somewhere in ci.yml, so deleting the comparison or the `exit 1`
left it green - the exact silent disablement it exists to catch. It now reads
the named step's own `run:` block and asserts each part the enforcement rests
on. Verified by replacing both `exit 1` lines with an echo: the test fails.

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

Copy link
Copy Markdown
Contributor Author

Round 2 — both findings accepted, fixed in 5d31439.

1. Prerelease baseline cannot pass. Correct, and it was worse than a mismatch: the two halves of this change would have deadlocked against each other. release.yml accepts prerelease tags, the flat-container index lists them alongside stable versions, and my freshness check took the last entry verbatim — so publishing 1.8.0-beta.1 would have demanded a baseline that Version.TryParse rejects. Neither leaving the baseline alone nor bumping it correctly could have passed CI.

The query now filters prereleases out (select(contains("-") | not)), so the baseline is always the newest stable release. That is the right answer independently of the parsing problem: a prerelease is not what a consumer restores by default, so it should not become the version every other PR is required to match. Filtering is also what keeps the baseline parseable, so Version.TryParse is now deliberate rather than accidental — the test comment says so. I also added a guard for an empty/null result rather than silently comparing against nothing.

Verified against a simulated index ["1.6.0", "1.7.0", "1.8.0-beta.1"] → 1.7.0, and against the live index → 1.7.0.

2. CI guard tests only strings. Also correct — asserting that two strings appear somewhere in the file would have stayed green with the comparison or the exit 1 deleted, which is the exact silent disablement the test exists to catch.

It now locates the step by name, extracts its own run: block, and asserts each part the enforcement actually rests on: the property lookup, the index URL, the prerelease filter, the "$baseline" != "$latest" comparison, and exit 1. Verified by replacing both exit 1 lines with an echo — the test fails.

I stopped short of executing the shell from the test. That would mean spawning bash on all three matrix legs for a build-configuration guard, and this file is explicit that its reach is deliberately shallow rather than a second, worse copy of the thing it guards. Scoping the assertions to the step and to the load-bearing tokens closes the gap you identified without that cost.

Full suite green on net9.0 + net10.0: 4152 + 217 per framework, 0 failed. CI was green on 526b554 including the new pack and baseline steps (the pack step runs in ~6s).

@tylerkron

Copy link
Copy Markdown
Contributor Author

/agentic_review

@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 5d31439

@tylerkron

Copy link
Copy Markdown
Contributor Author

Qodo-clean, CI green — ready for review.

Verified against head 5d31439: Qodo summary reads Bugs (0) / Rule violations (0) / Skill insights (0) with all three findings struck through as resolved, and all 3 qodo-code-review review threads are resolved (0 unresolved). Re-checked after a settle interval — both surfaces unchanged. CI run 33258223069 is green on that exact SHA across all three OS legs, with both new gates (Check the package validation baseline and Validate packaged API against the last published release) running and passing on the ubuntu leg.

Re-validated against current main: the branch already contains #703 (d75d82a, build-time IDE0161 + CA1707), so git merge origin/main is a no-op and the green run above already exercised the merged tree. Confirmed locally that dotnet pack src/Daqifi.Core/Daqifi.Core.csproj (without --no-build, so ApiCompat actually runs) builds and packs both net9.0 and net10.0 clean under the new analyzer rules — the new style enforcement does not trip package validation. The --no-build trap holds as documented: ci.yml packs without it so validation runs, while release.yml packs with it to keep the tag-stamped version, which is why the gate lives in CI.

@tylerkron

Copy link
Copy Markdown
Contributor Author

Negative control: the gate actually fails. Verified independently on 5d31439 (macOS, SDK 10.0.203) — everything prior was positive evidence only (the tree packs clean), which looks identical whether the gate works or does nothing.

Mutation — the exact scenario #636 describes: deleted the shipped public parameterless ctor TransportNotConnectedException() and removed its PublicAPI.Shipped.txt entry, so RS0016/RS0017 stay green and the in-repo file check cannot be what catches it. (Chosen because it has zero callers inside Daqifi.Core, so the deletion compiles clean — the build succeeded and the .nupkg was even produced; only validation failed.)

Result — dotnet pack exits 1, on both target frameworks:

error CP0002: Member 'Daqifi.Core.Communication.Transport.TransportNotConnectedException.TransportNotConnectedException()'
  exists on [Baseline] lib/net10.0/Daqifi.Core.dll but not on lib/net10.0/Daqifi.Core.dll
error CP0002: ... same on lib/net9.0/Daqifi.Core.dll
error : API breaking changes found. If those are intentional, the APICompat suppression file can be
  updated by rebuilding with '/p:ApiCompatGenerateSuppressionFile=true'

Compared against the real published daqifi.core.1.7.0.nupkg, confirming the baseline resolves to the nuget.org artifact rather than anything in the PR. Full cycle: packs clean → mutated fails → mutation reverted, tree clean at 5d31439, packs clean again.


One discrepancy, non-blocking. The comment on Ci_PacksTheLibrarySoPackageValidationActuallyRuns says --no-build makes the pack "silent". On SDK 10.0.203 it does not — dotnet pack --no-build against the mutated tree still reported both CP0002s and still exited 1. So validation is not skipped there, at least on this SDK. This doesn't weaken the PR: omitting --no-build in ci.yml is correct and strictly safer either way, and the assertion the test makes is still the right one. Only the stated rationale is narrower than claimed — likely SDK-version-dependent, so it may hold on whatever SDK it was originally observed on. Worth softening that comment if the file is touched again; not worth a push on its own.

@tylerkron
tylerkron added this pull request to the merge queue Aug 30, 2026
Merged via the queue into main with commit 4d1e957 Aug 30, 2026
6 of 8 checks passed
@tylerkron
tylerkron deleted the chore/636-package-validation branch August 30, 2026 01:56
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.

chore(api): nothing tracks the public API surface, but ADR 0002 makes source compatibility a promise

1 participant