Skip to content

Commit 2ce6092

Browse files
moisesjaclaude
andcommitted
M8a: quality & release gates (NFR-002/005/009 + CI/CD)
First of three M8 PRs (M8 = conformance + interop + samples + gates). Adds the pure-.NET quality and release gates — no external runtime. - Credentials.TestSupport: [FrTag] coverage attribute + RequirementIds (34 FR + 9 NFR). - Credentials.ArchitectureTests: no-draft-type surface (F3/NFR-005), static no-Newtonsoft reference closure (NFR-002), empty-<summary> catcher (NFR-009), net10/async gates (NFR-001/004), and the FrCoverage gate (every PRD §8 requirement has a tagged test). - Credentials.RoundTripTests: FR-003 byte fidelity per issued securing family. - Credentials.ConsumerProbe + tools/check-no-newtonsoft-closure.sh: authoritative package-level NFR-002 transitive-closure check. - PublicAPI analyzers (RS0016/17) + committed PublicAPI.{Shipped,Unshipped}.txt; ApiCompat tool (.config) + tools/check-api-compat.sh (skip-logged pre-release). - [FrTag] tags across the suite + new FR-004 (lazy projection memoization) and FR-021 (status-list set/clear round-trip) tests. - .github/workflows/ci.yml + release.yml. Adversarial pass fixed two hollow-gate findings: the ConsumerProbe stale-same-version-cache false-pass (now a unique per-run version via an env-var MSBuild property) and the static closure walk's used-reference blind spot (documented + recursion broadened; the package probe is authoritative). FrCoverage hardened to ignore commented-out tags. Build 0-warning under TreatWarningsAsErrors; 338 tests green (was 318). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7af8477 commit 2ce6092

55 files changed

Lines changed: 2522 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.config/dotnet-tools.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"version": 1,
3+
"isRoot": true,
4+
"tools": {
5+
"microsoft.dotnet.apicompat.tool": {
6+
"version": "10.0.301",
7+
"commands": [
8+
"apicompat"
9+
],
10+
"rollForward": false
11+
}
12+
}
13+
}

.editorconfig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ insert_final_newline = true
1313
dotnet_sort_system_directives_first = true
1414
dotnet_separate_import_directive_groups = false
1515

16+
# Public-API analyzers (RS0016/RS0017 surface tracking, NFR-005). RS0026/RS0027 forbid adding
17+
# overloads/constructors with optional parameters: the role API deliberately ships ergonomic
18+
# typed-object + wire-bytes overloads (e.g. IVerifier.VerifyCredentialAsync), which are unambiguous
19+
# (the leading parameter type differs) and predate M8 — so these two design opinions are disabled.
20+
dotnet_diagnostic.RS0026.severity = none
21+
dotnet_diagnostic.RS0027.severity = none
22+
1623
# Namespace
1724
csharp_style_namespace_declarations = file_scoped:suggestion
1825

.github/workflows/ci.yml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
# Cancel superseded runs on the same ref.
10+
concurrency:
11+
group: ci-${{ github.workflow }}-${{ github.ref }}
12+
cancel-in-progress: true
13+
14+
jobs:
15+
build-test:
16+
name: build + test (${{ matrix.os }})
17+
runs-on: ${{ matrix.os }}
18+
strategy:
19+
fail-fast: false
20+
matrix:
21+
os: [ubuntu-latest, windows-latest]
22+
steps:
23+
- uses: actions/checkout@v4
24+
- uses: actions/setup-dotnet@v4
25+
with:
26+
global-json-file: global.json
27+
- name: Restore
28+
run: dotnet restore Credentials.sln
29+
# Release build with TreatWarningsAsErrors is itself the XML-doc (CS1591) and public-API
30+
# (RS0016/RS0017) gate — a missing doc or an undeclared public member fails here.
31+
- name: Build (warnings-as-errors)
32+
run: dotnet build Credentials.sln -c Release --no-restore
33+
# Exclude the W3C conformance suite (it needs the Node test suite + the ASP.NET shim; runs in
34+
# conformance.yml). The filter is a no-op until those tests are tagged Category=Conformance in M8c.
35+
- name: Test
36+
run: dotnet test Credentials.sln -c Release --no-build --filter "Category!=Conformance"
37+
38+
no-newtonsoft:
39+
name: no-Newtonsoft consumer closure
40+
runs-on: ubuntu-latest
41+
steps:
42+
- uses: actions/checkout@v4
43+
- uses: actions/setup-dotnet@v4
44+
with:
45+
global-json-file: global.json
46+
# Packs Core + DI, restores the ConsumerProbe against them, asserts Newtonsoft is absent from
47+
# the transitive closure (NFR-002). The opt-in Credentials.Rdfc is the sanctioned carrier.
48+
- name: Assert default closure is Newtonsoft-free
49+
run: ./tools/check-no-newtonsoft-closure.sh
50+
51+
semver:
52+
name: API compatibility (semver)
53+
runs-on: ubuntu-latest
54+
steps:
55+
- uses: actions/checkout@v4
56+
- uses: actions/setup-dotnet@v4
57+
with:
58+
global-json-file: global.json
59+
- name: Restore tools
60+
run: dotnet tool restore
61+
# Compares each packed library against its last published nuget.org version (NFR-005). Skips a
62+
# package when it has no published baseline yet (pre-release) — logged, never falsely green.
63+
- name: ApiCompat against published baseline
64+
run: ./tools/check-api-compat.sh

.github/workflows/release.yml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: Release
2+
3+
# Tag-driven publish. Push a v-prefixed SemVer tag (e.g. v0.2.0) to build, gate, pack and push the
4+
# three NuGet packages. The package version is taken from the tag.
5+
on:
6+
push:
7+
tags: ['v*']
8+
9+
jobs:
10+
release:
11+
name: pack + publish
12+
runs-on: ubuntu-latest
13+
# Protected environment: configure NUGET_API_KEY (and any required reviewers) under
14+
# Settings → Environments → nuget-release.
15+
environment: nuget-release
16+
permissions:
17+
contents: read
18+
steps:
19+
- uses: actions/checkout@v4
20+
- uses: actions/setup-dotnet@v4
21+
with:
22+
global-json-file: global.json
23+
24+
- name: Derive version from tag
25+
id: version
26+
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
27+
28+
- name: Restore + tools
29+
run: |
30+
dotnet restore Credentials.sln
31+
dotnet tool restore
32+
33+
# Full gate before anything is published.
34+
- name: Build (warnings-as-errors)
35+
run: dotnet build Credentials.sln -c Release --no-restore -p:CredentialsVersion=${{ steps.version.outputs.version }}
36+
- name: Test
37+
run: dotnet test Credentials.sln -c Release --no-build --filter "Category!=Conformance"
38+
- name: No-Newtonsoft closure
39+
run: ./tools/check-no-newtonsoft-closure.sh
40+
- name: ApiCompat against published baseline
41+
run: ./tools/check-api-compat.sh
42+
43+
- name: Pack (.nupkg + .snupkg)
44+
run: |
45+
for proj in \
46+
src/Credentials.Core/Credentials.Core.csproj \
47+
src/Credentials.Extensions.DependencyInjection/Credentials.Extensions.DependencyInjection.csproj \
48+
src/Credentials.Rdfc/Credentials.Rdfc.csproj ; do
49+
dotnet pack "$proj" -c Release --no-build \
50+
-p:CredentialsVersion=${{ steps.version.outputs.version }} \
51+
-o ./artifacts/packages
52+
done
53+
54+
- name: Upload package artifacts
55+
uses: actions/upload-artifact@v4
56+
with:
57+
name: packages
58+
path: ./artifacts/packages/*.*nupkg
59+
60+
- name: Push to nuget.org
61+
env:
62+
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
63+
run: |
64+
dotnet nuget push "./artifacts/packages/*.nupkg" \
65+
--api-key "$NUGET_API_KEY" \
66+
--source https://api.nuget.org/v3/index.json \
67+
--skip-duplicate

CHANGELOG.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,69 @@ All notable changes to `credentials-dotnet` are documented here. The format is b
66

77
## [Unreleased]
88

9+
### Added — Milestone M8a (Quality & release gates)
10+
11+
The first of three M8 PRs (M8 = conformance + interop + samples + gates). M8a lands the pure-.NET
12+
quality and release gates — no external runtime — closing the package/surface/doc halves of NFR-002,
13+
NFR-005, NFR-009 empirically and wiring CI/CD. Test count **318 → 338**; build stays 0-warning under
14+
`TreatWarningsAsErrors`.
15+
16+
- **`Credentials.TestSupport`** — a shared, non-packable library holding the `[FrTag]` requirement-
17+
coverage attribute and `RequirementIds`, the single source of truth for the PRD §8 set (34 FR + 9 NFR).
18+
- **`Credentials.ArchitectureTests`** — invariant gates inspected by metadata only
19+
(`System.Reflection.MetadataLoadContext`, so the opt-in Credentials.Rdfc is examined without loading
20+
its Newtonsoft into the test host):
21+
- `PublicSurface_ExposesNoDataProofsType` (FR-051 / NFR-005, F3) — no `DataProofsDotnet` type may
22+
appear in any public/protected signature of the three libraries (return/parameter/field/property/
23+
generic-argument/base/interface vectors all covered).
24+
- `DefaultLibrary_ReferenceClosure_HasNoNewtonsoft` (NFR-002, static half) — the compile-time
25+
reference closure of Core + DI is Newtonsoft-free. Documented scope: catches a *used* Newtonsoft
26+
type; the authoritative package-level guarantee is the ConsumerProbe (below).
27+
- `PublicSurface_EveryDocumentedMember_HasNonEmptySummary` (NFR-009) — complements CS1591-as-error
28+
(missing docs) by catching *empty* `<summary>` on public-surface members.
29+
- `RoleMethods_NamedAsync_ReturnTaskOrValueTask` (NFR-004) and `Library_TargetsNet10` (NFR-001).
30+
- `EveryRequirement_HasAtLeastOneTaggedTest` (the **FrCoverage gate**) — every PRD §8 requirement has
31+
≥1 `[FrTag]`-tagged test (deferring NFR-007 to M8c with a logged reason); a typo'd/unknown id fails.
32+
- **`Credentials.RoundTripTests`** (FR-003) — byte-fidelity DoD per securing family this engine issues
33+
end-to-end (unsecured received-bytes verbatim; embedded DI in JCS+RDFC byte-stable with exactly one
34+
`proof`; JOSE/SD-JWT verbatim compact + signed-payload == source bytes; COSE verbatim wire bytes;
35+
H1 `<>&`/non-BMP-emoji value fidelity).
36+
- **`Credentials.ConsumerProbe`** + `tools/check-no-newtonsoft-closure.sh` (NFR-002, authoritative
37+
package half) — packs Core + DI, restores a real package consumer against a local feed, and asserts
38+
Newtonsoft is absent from the transitive closure (`dotnet list package --include-transitive`).
39+
- **Public-API surface tracking (NFR-005 semver)**`Microsoft.CodeAnalysis.PublicApiAnalyzers`
40+
(RS0016/RS0017) wired for the three shippable libraries via `Directory.Build.targets`, with the
41+
current surface committed to `PublicAPI.{Shipped,Unshipped}.txt` (749 / 26 / 16 entries). Any public-
42+
surface change now fails the build until the API file is updated — a reviewable text diff. RS0026/
43+
RS0027 (the "no overloads with optional parameters" opinion) are disabled for the deliberate, in-place
44+
ergonomic role overloads. `Microsoft.DotNet.ApiCompat.Tool` (local tool manifest) +
45+
`tools/check-api-compat.sh` add the package-compat check against the last published version (skip-
46+
logged pre-release; honest, never falsely green).
47+
- **`[FrTag]` tagging** — a representative existing test tagged for each of the 42 currently-coverable
48+
requirements, plus two new tests: FR-004 (lazy projections memoized over the frozen document, proven
49+
via `ReferenceEquals`) and FR-021 (`StatusListManager` set→re-produce→read and clear→re-produce→read).
50+
- **CI/CD**`.github/workflows/ci.yml` (ubuntu+windows build/test matrix with `TreatWarningsAsErrors`
51+
as the XML-doc + public-API gate, plus `no-newtonsoft` and `semver` jobs) and `release.yml` (tag-
52+
driven pack + gate + push under a protected `nuget-release` environment).
53+
54+
#### Security & hardening (M8a adversarial review)
55+
56+
An adversarial pass attacked each gate to confirm it has teeth (not hollow). The surface, semver
57+
(RS0016), doc (CS1591/empty-summary), and FrCoverage (missing-tag + unknown-id) gates all failed
58+
correctly when defeat was attempted. Two real findings were fixed:
59+
60+
- **ConsumerProbe was hollow (stale-cache false-pass).** Re-packing the fixed version `0.1.0` with new
61+
dependencies served *stale* package metadata from nuget's id+version-keyed cache, so a Newtonsoft
62+
dependency added to Core was invisible to the closure check — it always reported the first run's
63+
result. Fixed by packing under a unique per-run version delivered as an environment-variable MSBuild
64+
property (consistent across restore/build/`dotnet list`, which does not accept `-p:`). Verified: the
65+
fixed gate now reports the violation when Newtonsoft is present and clean otherwise.
66+
- **The static reference-closure check has an inherent blind spot** — the C# compiler only records
67+
references to *used* assemblies, so an unused-yet-declared `PackageReference` carrying Newtonsoft is
68+
invisible to it. Documented honestly; recursion broadened to every non-BCL assembly (so a *used*
69+
Newtonsoft edge hidden behind an intermediate is still caught); the package-level ConsumerProbe is the
70+
authoritative NFR-002 gate. The FrCoverage scan was also hardened to ignore commented-out `[FrTag]`s.
71+
972
### Added — Milestone M7 (VCDM 1.1 verify)
1073

1174
- **VCDM 1.1 verification (FR-044 / D8):** the verifier now accepts **VCDM 1.1** credentials and

Credentials.sln

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Credentials.Rdfc", "src\Cre
1919
EndProject
2020
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Credentials.Rdfc.Tests", "tests\Credentials.Rdfc.Tests\Credentials.Rdfc.Tests.csproj", "{9679FB89-FE93-4E8E-8078-E001A99452BC}"
2121
EndProject
22+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Credentials.TestSupport", "tests\Credentials.TestSupport\Credentials.TestSupport.csproj", "{840E889E-8408-4A2D-A9AD-80F9433069F2}"
23+
EndProject
24+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Credentials.ArchitectureTests", "tests\Credentials.ArchitectureTests\Credentials.ArchitectureTests.csproj", "{7495F11C-6ACB-445A-BE8F-A73969EB8651}"
25+
EndProject
26+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Credentials.RoundTripTests", "tests\Credentials.RoundTripTests\Credentials.RoundTripTests.csproj", "{95D74A3F-243F-4CB0-945F-B47EABC672CF}"
27+
EndProject
2228
Global
2329
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2430
Debug|Any CPU = Debug|Any CPU
@@ -101,6 +107,42 @@ Global
101107
{9679FB89-FE93-4E8E-8078-E001A99452BC}.Release|x64.Build.0 = Release|Any CPU
102108
{9679FB89-FE93-4E8E-8078-E001A99452BC}.Release|x86.ActiveCfg = Release|Any CPU
103109
{9679FB89-FE93-4E8E-8078-E001A99452BC}.Release|x86.Build.0 = Release|Any CPU
110+
{840E889E-8408-4A2D-A9AD-80F9433069F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
111+
{840E889E-8408-4A2D-A9AD-80F9433069F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
112+
{840E889E-8408-4A2D-A9AD-80F9433069F2}.Debug|x64.ActiveCfg = Debug|Any CPU
113+
{840E889E-8408-4A2D-A9AD-80F9433069F2}.Debug|x64.Build.0 = Debug|Any CPU
114+
{840E889E-8408-4A2D-A9AD-80F9433069F2}.Debug|x86.ActiveCfg = Debug|Any CPU
115+
{840E889E-8408-4A2D-A9AD-80F9433069F2}.Debug|x86.Build.0 = Debug|Any CPU
116+
{840E889E-8408-4A2D-A9AD-80F9433069F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
117+
{840E889E-8408-4A2D-A9AD-80F9433069F2}.Release|Any CPU.Build.0 = Release|Any CPU
118+
{840E889E-8408-4A2D-A9AD-80F9433069F2}.Release|x64.ActiveCfg = Release|Any CPU
119+
{840E889E-8408-4A2D-A9AD-80F9433069F2}.Release|x64.Build.0 = Release|Any CPU
120+
{840E889E-8408-4A2D-A9AD-80F9433069F2}.Release|x86.ActiveCfg = Release|Any CPU
121+
{840E889E-8408-4A2D-A9AD-80F9433069F2}.Release|x86.Build.0 = Release|Any CPU
122+
{7495F11C-6ACB-445A-BE8F-A73969EB8651}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
123+
{7495F11C-6ACB-445A-BE8F-A73969EB8651}.Debug|Any CPU.Build.0 = Debug|Any CPU
124+
{7495F11C-6ACB-445A-BE8F-A73969EB8651}.Debug|x64.ActiveCfg = Debug|Any CPU
125+
{7495F11C-6ACB-445A-BE8F-A73969EB8651}.Debug|x64.Build.0 = Debug|Any CPU
126+
{7495F11C-6ACB-445A-BE8F-A73969EB8651}.Debug|x86.ActiveCfg = Debug|Any CPU
127+
{7495F11C-6ACB-445A-BE8F-A73969EB8651}.Debug|x86.Build.0 = Debug|Any CPU
128+
{7495F11C-6ACB-445A-BE8F-A73969EB8651}.Release|Any CPU.ActiveCfg = Release|Any CPU
129+
{7495F11C-6ACB-445A-BE8F-A73969EB8651}.Release|Any CPU.Build.0 = Release|Any CPU
130+
{7495F11C-6ACB-445A-BE8F-A73969EB8651}.Release|x64.ActiveCfg = Release|Any CPU
131+
{7495F11C-6ACB-445A-BE8F-A73969EB8651}.Release|x64.Build.0 = Release|Any CPU
132+
{7495F11C-6ACB-445A-BE8F-A73969EB8651}.Release|x86.ActiveCfg = Release|Any CPU
133+
{7495F11C-6ACB-445A-BE8F-A73969EB8651}.Release|x86.Build.0 = Release|Any CPU
134+
{95D74A3F-243F-4CB0-945F-B47EABC672CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
135+
{95D74A3F-243F-4CB0-945F-B47EABC672CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
136+
{95D74A3F-243F-4CB0-945F-B47EABC672CF}.Debug|x64.ActiveCfg = Debug|Any CPU
137+
{95D74A3F-243F-4CB0-945F-B47EABC672CF}.Debug|x64.Build.0 = Debug|Any CPU
138+
{95D74A3F-243F-4CB0-945F-B47EABC672CF}.Debug|x86.ActiveCfg = Debug|Any CPU
139+
{95D74A3F-243F-4CB0-945F-B47EABC672CF}.Debug|x86.Build.0 = Debug|Any CPU
140+
{95D74A3F-243F-4CB0-945F-B47EABC672CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
141+
{95D74A3F-243F-4CB0-945F-B47EABC672CF}.Release|Any CPU.Build.0 = Release|Any CPU
142+
{95D74A3F-243F-4CB0-945F-B47EABC672CF}.Release|x64.ActiveCfg = Release|Any CPU
143+
{95D74A3F-243F-4CB0-945F-B47EABC672CF}.Release|x64.Build.0 = Release|Any CPU
144+
{95D74A3F-243F-4CB0-945F-B47EABC672CF}.Release|x86.ActiveCfg = Release|Any CPU
145+
{95D74A3F-243F-4CB0-945F-B47EABC672CF}.Release|x86.Build.0 = Release|Any CPU
104146
EndGlobalSection
105147
GlobalSection(SolutionProperties) = preSolution
106148
HideSolutionNode = FALSE
@@ -112,5 +154,8 @@ Global
112154
{EE051155-BE4D-4F90-8CF3-1A9AF5E1898E} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
113155
{90AB853F-9033-408D-A1A8-37C8E18FA680} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
114156
{9679FB89-FE93-4E8E-8078-E001A99452BC} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
157+
{840E889E-8408-4A2D-A9AD-80F9433069F2} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
158+
{7495F11C-6ACB-445A-BE8F-A73969EB8651} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
159+
{95D74A3F-243F-4CB0-945F-B47EABC672CF} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
115160
EndGlobalSection
116161
EndGlobal

Directory.Build.targets

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,16 @@
1111
<ItemGroup Condition="'$(IsPackable)' != 'false' AND Exists('$(MSBuildThisFileDirectory)README.md')">
1212
<None Include="$(MSBuildThisFileDirectory)README.md" Pack="true" PackagePath="\" Visible="false" />
1313
</ItemGroup>
14+
15+
<!--
16+
Public-API surface tracking for the shippable libraries only (IsPackable != false). RS0016 fails
17+
the build when a public member is missing from PublicAPI.{Shipped,Unshipped}.txt and RS0017 when a
18+
declared one is removed, so the public surface cannot change without a reviewable text diff
19+
(NFR-005). Imported here (after the project body) so $(IsPackable) is resolved.
20+
-->
21+
<ItemGroup Condition="'$(IsPackable)' != 'false'">
22+
<PackageReference Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" PrivateAssets="all" />
23+
<AdditionalFiles Include="$(MSBuildProjectDirectory)\PublicAPI.Shipped.txt" />
24+
<AdditionalFiles Include="$(MSBuildProjectDirectory)\PublicAPI.Unshipped.txt" />
25+
</ItemGroup>
1426
</Project>

Directory.Packages.props

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@
4242
<!-- Source Link -->
4343
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="10.0.300" />
4444

45+
<!-- Public-API surface tracking (RS0016/RS0017): every public member must be declared in the
46+
committed PublicAPI.{Shipped,Unshipped}.txt, so an accidental surface change fails the build
47+
and an intentional one shows up as a reviewable text diff (NFR-005 semver guard). -->
48+
<PackageVersion Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="4.14.0" />
49+
50+
<!-- Architecture/surface tests: inspect built assemblies' public surface (incl. the opt-in
51+
Credentials.Rdfc) by metadata only, without runtime-loading them — so the no-Newtonsoft
52+
runtime-closure assertion stays valid in the same test host. -->
53+
<PackageVersion Include="System.Reflection.MetadataLoadContext" Version="10.0.8" />
54+
4555
<!-- Testing -->
4656
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
4757
<PackageVersion Include="xunit" Version="2.9.3" />

src/Credentials.Core/PublicAPI.Shipped.txt

Whitespace-only changes.

0 commit comments

Comments
 (0)