Skip to content

Keep shipped public API entries when recording removals - #4644

Open
thomhurst wants to merge 13 commits into
mainfrom
issue-4638-publicapi-removed-markers
Open

Keep shipped public API entries when recording removals#4644
thomhurst wants to merge 13 commits into
mainfrom
issue-4638-publicapi-removed-markers

Conversation

@thomhurst

@thomhurst thomhurst commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Summary

Adopts option (a) from #4638: the public API baseline sync now follows the Microsoft.CodeAnalysis.PublicApiAnalyzers lifecycle. A confirmed removal (or the old half of a signature change) keeps its line in PublicAPI.Shipped.txt and is retired by a *REMOVED* marker in PublicAPI.Unshipped.txt; only a release "ship" step collapses the pair. Previously the sync deleted the shipped line in the same commit, so every marker it wrote was orphaned from birth.

  • Merge-PublicApiBaselineSnapshot.ps1 retains every original shipped entry, and only carries an existing *REMOVED* marker forward while its entry is still shipped and still absent. The writer therefore cannot produce an orphan; the RS0017-confirmed removal gate is unchanged and still decides when a marker is written.
  • Write-PublicApiChangeSummary.ps1 treats a *REMOVED* marker as retiring its shipped entry when it computes the active surface, so the generated-PR summary (Report assembly-wide API changes in generated PR summaries #4576) keeps reporting removals and signature changes under the new shape.
  • scripts/Assert-PublicApiBaselines.ps1 (fast-fail CI) now rejects any *REMOVED* marker whose entry is no longer shipped, listing the offending file and marker.
  • One-time migration: the 19,617 markers on main whose shipped entry had already been deleted are removed from 54 PublicAPI.Unshipped.txt files. They were inert to the analyzer and could never be reconciled; their shipped lines were already gone, so deleting the markers rather than re-inserting thousands of dead shipped entries keeps the baselines consistent with the least churn. Markers that still have a shipped entry (92 across ModularPipelines and ModularPipelines.GitHub) are kept.

The generator source fingerprint changes, so the next Generate CLI Options run regenerates every tool's baselines from main in the new shape. The release-time "ship" step that collapses marker/shipped pairs and empties PublicAPI.Unshipped.txt (#4244) does not exist yet; it belongs with the v4 release work (#3997) rather than this fix. Truly empty snapshot/removal inputs are handled separately by #4609; the new empty-removals test uses a header-only file so the two changes stay independent.

Test plan

  • Test-MergePublicApiBaselineSnapshot.ps1 (removal, signature change, no removals, idempotency)
  • Test-SyncPublicApiBaselines.ps1
  • Test-WritePublicApiChangeSummary.ps1 (2 added, 2 removed, 1 changed signature under the new shape)
  • scripts/Test-AssertPublicApiBaselines.ps1 (marker with shipped entry passes, orphan fails and is reported)
  • scripts/Assert-PublicApiBaselines.ps1 against this tree: 61 packages, no orphans
  • dotnet build src/ModularPipelines.Chocolatey -c Release: 0 warnings, 0 errors (no RS0016/RS0017/RS0024)
  • CI Generate CLI Options run after merge produces *REMOVED* markers alongside retained shipped entries

Closes #4638

Summary by CodeRabbit

  • Breaking Changes
    • Consolidated public context and command-result types across integrations.
    • Renamed option types and members, updated required parameters and return types, and removed obsolete APIs.
    • Updated extension methods to use the current pipeline context type.
  • Bug Fixes
    • Public API baseline validation now detects orphaned removal markers, duplicate entries, whitespace issues, and reports combined errors.
    • Baseline merging now preserves valid shipped entries and filters stale markers.
  • Tests
    • Expanded coverage for valid, orphaned, duplicate, and whitespace-affected API baseline entries.
    • Added validation for removal-marker handling and baseline synchronization.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 5, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-06T07:31:00.560671Z 1fc04bc New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@thomhurst
thomhurst force-pushed the issue-4638-publicapi-removed-markers branch from 22c3e08 to dcf3216 Compare September 5, 2026 23:42
@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

Next included review available in 2 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: d4269f8e-fd3f-4796-93ff-a8093cb6c718

📥 Commits

Reviewing files that changed from the base of the PR and between 1c9464c and 1fc04bc.

📒 Files selected for processing (3)
  • src/ModularPipelines.Flux/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Go/PublicAPI.Unshipped.txt
  • src/ModularPipelines/PublicAPI.Unshipped.txt

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 29b667ae-c1fb-4f4f-8ebc-72913a1a7074

📥 Commits

Reviewing files that changed from the base of the PR and between 89864c6 and 1c9464c.

📒 Files selected for processing (7)
  • scripts/Assert-PublicApiBaselines.ps1
  • scripts/Test-AssertPublicApiBaselines.ps1
  • src/ModularPipelines.Homebrew/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Pulumi/PublicAPI.Unshipped.txt
  • src/ModularPipelines/PublicAPI.Unshipped.txt
  • tools/ModularPipelines.OptionsGenerator/scripts/Merge-PublicApiBaselineSnapshot.ps1
  • tools/ModularPipelines.OptionsGenerator/scripts/Test-MergePublicApiBaselineSnapshot.ps1

Included review availability: Your plan provides up to 4 included reviews per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The PR updates public API baseline tooling and tests to preserve shipped entries for removals, reject orphaned *REMOVED* markers, and exclude retired entries from active API summaries. It also regenerates package API tracking files for namespace, signature, and removal changes.

Changes

Public API baseline lifecycle

Layer / File(s) Summary
Centralize and validate removal markers
scripts/PublicApiRemovedMarker.ps1, scripts/Assert-PublicApiBaselines.ps1, scripts/Test-PublicApiRemovedMarker.ps1, scripts/Test-AssertPublicApiBaselines.ps1, .github/workflows/dotnet.yml, .github/workflows/generate-cli-options.yml, scripts/GeneratedOptionsProvenance.ps1
Shared marker helpers are used by baseline validation. Validation reports orphaned markers and duplicate entries. Tests and workflows cover these checks.
Merge baselines and summarize retired APIs
tools/ModularPipelines.OptionsGenerator/scripts/Merge-PublicApiBaselineSnapshot.ps1, tools/ModularPipelines.OptionsGenerator/scripts/Write-PublicApiChangeSummary.ps1, tools/ModularPipelines.OptionsGenerator/scripts/Test-MergePublicApiBaselineSnapshot.ps1, tools/ModularPipelines.OptionsGenerator/scripts/Test-SyncPublicApiBaselines.ps1, tools/ModularPipelines.OptionsGenerator/scripts/Test-WritePublicApiChangeSummary.ps1
The merge retains shipped declarations and writes markers only for shipped entries. Summary generation excludes retired entries from active APIs and reports added and removed counts.
Update package API tracking baselines
src/ModularPipelines.*/PublicAPI.Unshipped.txt
Package tracking files record namespace moves, CommandResult changes, option-shape changes, removed members, and extension-method changes.

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

Merge Risk: ⚪ Minimal · up to 1c946

This updates public API baseline synchronization to retain shipped declarations for removals while validating markers and duplicate entries. The supplied coverage indicates the revised baseline lifecycle is ready to merge.

Poem

A rabbit checks each API line,
Keeps shipped markers in a row,
Finds orphaned signs that drifted free,
Updates paths from old to new,
And hops through tests that make it so.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: retaining shipped public API entries while recording removals.
Linked Issues check ✅ Passed The changes implement issue #4638: they retain shipped entries, preserve valid REMOVED markers, remove orphaned markers, update merge and sync tests, preserve removal reporting, and add validation a…
Out of Scope Changes check ✅ Passed The changes are within scope. Baseline migrations, shared marker helpers, tests, workflow updates, provenance updates, and API tracking changes directly support the public API lifecycle requirements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-4638-publicapi-removed-markers

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Review: Keep shipped public API entries when recording removals

This PR reworks the public-API baseline lifecycle so shipped entries are preserved and *REMOVED* markers are only written/kept when there's a matching shipped declaration, plus a one-time migration that purges ~19,600 orphaned markers across all tool packages. I traced the core scripts (Merge-PublicApiBaselineSnapshot.ps1, Assert-PublicApiBaselines.ps1, Write-PublicApiChangeSummary.ps1) against their updated test suites and spot-checked the migrated data — the algorithm is sound and well covered by tests.

Two smaller issues survived verification:

1. Duplicate marker left in the migrated baseline
src/ModularPipelines/PublicAPI.Unshipped.txt:20 and :32 are an exact duplicate line:

*REMOVED*ModularPipelines.Distributed.IDistributedCoordinator.DequeueModuleAsync(System.Collections.Generic.IReadOnlySet<string!>! workerCapabilities, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<ModularPipelines.Distributed.ModuleAssignment?>!

The migration's stated goal was to make these baseline files internally consistent, but it left this adjacent duplicate in the very file it rewrote. Worth deduplicating now (e.g. via Sort-Object -Unique) rather than leaving it for a future Sync run's HashSet to quietly clean up — an unnecessary diff/noise source in the meantime.

2. *REMOVED* prefix/parsing logic duplicated across three scripts
$removedPrefix = '*REMOVED*' plus the .Substring($removedPrefix.Length) stripping logic is independently reimplemented in:

  • scripts/Assert-PublicApiBaselines.ps1 (new orphan check)
  • tools/ModularPipelines.OptionsGenerator/scripts/Merge-PublicApiBaselineSnapshot.ps1
  • tools/ModularPipelines.OptionsGenerator/scripts/Write-PublicApiChangeSummary.ps1

If the marker convention ever changes (different prefix, different trim rule), an editor who updates one copy and misses the other two would silently reintroduce the exact orphaned-marker bug this PR fixes. Consider factoring this into a small shared .ps1 helper (dot-sourced or a module) that all three scripts import, so the convention has one source of truth.

Neither issue blocks the core fix, but both are worth addressing before merge given the PR's own goal is baseline consistency and preventing marker drift.

@thomhurst
thomhurst force-pushed the issue-4638-publicapi-removed-markers branch from dcf3216 to 4e68dca Compare September 5, 2026 23:55
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Review: Keep shipped public API entries when recording removals

Re-reviewed at HEAD 4e68dca. This PR reworks the public-API baseline lifecycle so shipped entries are preserved and *REMOVED* markers are only written/kept when a matching shipped declaration exists, plus a one-time migration purging orphaned markers. The core algorithm in Merge-PublicApiBaselineSnapshot.ps1, Assert-PublicApiBaselines.ps1, and Write-PublicApiChangeSummary.ps1 is sound and matches the test fixtures.

Both issues raised in the previous review are still present and unaddressed at this HEAD:

1. Duplicate marker still in the migrated baseline
src/ModularPipelines/PublicAPI.Unshipped.txt:20 and :32 remain an exact duplicate:

*REMOVED*ModularPipelines.Distributed.IDistributedCoordinator.DequeueModuleAsync(System.Collections.Generic.IReadOnlySet<string!>! workerCapabilities, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<ModularPipelines.Distributed.ModuleAssignment?>!

Since this PR's own goal is baseline consistency (and it added an orphan-marker check specifically to catch drift like this), leaving a duplicate in the very file the migration rewrote undercuts that goal. Worth deduplicating now (e.g. Sort-Object -Unique in the migration script) rather than relying on a future Sync run's HashSet to silently absorb it.

2. *REMOVED* prefix/parsing logic still duplicated across three scripts
$removedPrefix = '*REMOVED*' plus the .StartsWith(...) / .Substring($removedPrefix.Length) convention is still independently implemented in:

  • scripts/Assert-PublicApiBaselines.ps1 (lines 47, 52, 63)
  • tools/ModularPipelines.OptionsGenerator/scripts/Merge-PublicApiBaselineSnapshot.ps1 (lines 12, 62, 82, 88, 92)
  • tools/ModularPipelines.OptionsGenerator/scripts/Write-PublicApiChangeSummary.ps1 (lines 29, 43-44)

This is the exact kind of triplication that lets marker-convention drift reintroduce the orphaned-marker bug this PR fixes — an editor changing the prefix or trim rule in one script and missing the other two would silently break the invariant again. A small shared .ps1 helper (dot-sourced, or a .psm1 module imported by all three) exposing something like Get-RemovedMarkerPrefix, Test-RemovedMarker, and Get-RemovedMarkerEntry would make the convention single-sourced and put future changes in one place instead of three.

Minor (new, non-blocking): scripts/Assert-PublicApiBaselines.ps1 re-derives $projectDirectory (and effectively $unshippedPath) in the orphan-detection loop (line 49-50) that the preceding missing-baseline loop (line 29) already computed. Not a correctness issue — the missing-baseline throw guarantees the files exist by the time the second loop runs — but folding both checks into a single pass over $packageProjects would avoid the duplicated path-building work and keep future per-project checks in one place.

Neither of the two carried-over issues blocks correctness of the core fix, but both were called out as pre-merge items given the PR's stated goal of eliminating baseline drift, and neither has been addressed since.

@thomhurst

Copy link
Copy Markdown
Owner Author

Addressed both carried-over findings at exact head a68ce8d.

1. *REMOVED* convention triplicated. Added scripts/PublicApiRemovedMarker.ps1 exposing Get-RemovedMarkerPrefix, Test-RemovedMarker, Get-RemovedMarkerEntry and New-RemovedMarker, and dot-sourced it from Assert-PublicApiBaselines.ps1, Merge-PublicApiBaselineSnapshot.ps1 and Write-PublicApiChangeSummary.ps1. None of the three defines the prefix or its parse rule any more. The helper is covered by scripts/Test-PublicApiRemovedMarker.ps1 (prefix, round-trip, ordinal/no-trim rejection cases, guard on non-markers).

2. Duplicated per-project path derivation in the assert script. The missing-baseline and orphaned-marker checks are now one pass over $packageProjects: each project derives its two baseline paths once, a project with a missing file is recorded and skipped, and the throw order (missing first, then orphans) is unchanged so the existing test still discriminates both failures.

Also wired Test-PublicApiRemovedMarker.ps1 and the pre-existing Test-AssertPublicApiBaselines.ps1 (which CI never ran) into dotnet.yml ahead of the baseline coverage step.

Local runs: the five script tests (Test-PublicApiRemovedMarker, Test-AssertPublicApiBaselines, Test-MergePublicApiBaselineSnapshot, Test-SyncPublicApiBaselines, Test-WritePublicApiChangeSummary) all pass, and Assert-PublicApiBaselines.ps1 verifies 61 package projects on this head.

@claude please re-review exact head a68ce8d.

@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: a68ce8d20a

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

@thomhurst
thomhurst force-pushed the issue-4638-publicapi-removed-markers branch 2 times, most recently from 8fc873a to 42a24ec Compare September 6, 2026 00:31
@thomhurst

Copy link
Copy Markdown
Owner Author

Correction and resync at exact head 42a24ec49e. My earlier resync to 8fc873a441 was made from a stale local branch and dropped the a68ce8d20a refactor (shared scripts/PublicApiRemovedMarker.ps1 helper, single-pass assert script, CI wiring for the two script tests). That commit is restored: the branch is now fix(publicapi) + refactor(publicapi) rebased onto current main (Argo CD baseline re-migrated after #4535), plus one more commit that adds scripts/PublicApiRemovedMarker.ps1 to Get-GeneratedOptionsSourcePath and the Generate CLI Options push paths, as the Codex thread asked. Local: all six baseline/marker/provenance script tests pass and Assert-PublicApiBaselines.ps1 verifies 61 packages. @claude please re-review exact head 42a24ec.

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Review: Keep shipped public API entries when recording removals

Re-reviewed at HEAD 42a24ec. The core algorithm change is sound: Merge-PublicApiBaselineSnapshot.ps1 now keeps every original shipped entry, only adds a *REMOVED* marker when RS0017 has actually confirmed the removal, and only carries an existing marker forward while its entry is still shipped and still absent from the current snapshot — so it can no longer produce an orphan, and it correctly drops a marker if the API reappears. Write-PublicApiChangeSummary.ps1 and Assert-PublicApiBaselines.ps1 treat the marker consistently with that model.

Both issues raised in the earlier automated review rounds on this thread are now handled:

  • Marker convention triplication — fixed. scripts/PublicApiRemovedMarker.ps1 is the single source for the *REMOVED* prefix and its parse rule, dot-sourced from Assert-PublicApiBaselines.ps1, Merge-PublicApiBaselineSnapshot.ps1, and Write-PublicApiChangeSummary.ps1, with its own test file. It's also wired into GeneratedOptionsProvenance.ps1 and generate-cli-options.yml so the Generate CLI Options job re-triggers when it changes.
  • Duplicated per-project path derivation in the assert script — fixed, now a single pass building both baseline paths per project.
  • CI wiring for Test-PublicApiRemovedMarker.ps1 and Test-AssertPublicApiBaselines.ps1 was added to dotnet.yml ahead of Assert-PublicApiBaselines.ps1 — confirmed present at lines 105–112.

One finding from the first two review rounds is still unresolved and looks build-breaking, not cosmetic.

src/ModularPipelines/PublicAPI.Unshipped.txt lines 20 and 32 are still an exact duplicate line at this HEAD:

*REMOVED*ModularPipelines.Distributed.IDistributedCoordinator.DequeueModuleAsync(System.Collections.Generic.IReadOnlySet<string!>! workerCapabilities, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<ModularPipelines.Distributed.ModuleAssignment?>!

This was flagged twice before as a leftover from the one-time migration. Looking back at the thread, the author's "addressed both carried-over findings" reply actually fixed a different pairing (marker triplication + assert-script path duplication) — the duplicate-line finding itself was never touched, and it's still there verbatim.

This isn't just noise to clean up later: Directory.Build.props sets TreatWarningsAsErrors=true for CI builds (line 23) and, specifically for the ModularPipelines project, force-enables RunAnalyzers/RunAnalyzersDuringBuild in CI (lines 48–52) with Microsoft.CodeAnalysis.PublicApiAnalyzers referenced (line 45). Only RS0026/RS0041/a handful of MPG* rules are pulled out of "as error" treatment for that project (line 54, 56) — RS0024 ("the contents of the public API files are invalid"), which is the diagnostic PublicApiAnalyzers raises for a duplicate entry within the same file, is not excluded. ModularPipelines.slnx (which contains this project) is built directly in dotnet.yml (e.g. lines 298, 676). I wasn't able to actually execute a build in this review sandbox to get the compiler's diagnostic text (the run required interactive approval that isn't available here), so please verify with a real build before merging, but the config alone points to this duplicate turning into a hard CI failure on the exact project this PR is trying to fix.

Suggested fix: route the one-time migration output through the same dedupe path Merge-PublicApiBaselineSnapshot.ps1 already uses (HashSet + Sort-Object/[Array]::Sort) instead of hand-editing/concatenating lines, so a duplicate like this can't survive the migration silently. A couple of other migrated files (ModularPipelines.GitHub, ModularPipelines.DotNet) were spot-checked and came back clean, so this looks isolated to this one file rather than systemic — but worth a sort | uniq -d sweep across all 54 touched PublicAPI.Unshipped.txt files before merging to be sure.

@thomhurst
thomhurst force-pushed the issue-4638-publicapi-removed-markers branch from 42a24ec to beb88f6 Compare September 6, 2026 00:59
@thomhurst

Copy link
Copy Markdown
Owner Author

Addressed the remaining finding at exact head beb88f6e8e (rebased onto current main after #4462, conflict-free). The duplicated *REMOVED*...DequeueModuleAsync line in src/ModularPipelines/PublicAPI.Unshipped.txt predates this PR (it is present on main too), so the migration did not create it; it is now removed, and a sort | uniq -d sweep across every PublicAPI.*.txt finds no other repeats. To keep that true, Assert-PublicApiBaselines.ps1 now also fails on any entry listed twice in a baseline (bulk set-count fast path, per-line report only on mismatch; the tree check still runs in ~2.6 s for 61 packages), with a case in Test-AssertPublicApiBaselines.ps1. All six script tests pass locally. @claude please re-review exact head beb88f6.

@coderabbitai

coderabbitai Bot commented Sep 6, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@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: 2

🤖 Prompt for all review comments with AI agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. 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 `@scripts/Assert-PublicApiBaselines.ps1`:
- Around line 81-82: Update the shipped lookup in the marker validation flow to
use the exact unmodified result of Get-RemovedMarkerEntry, matching
Merge-PublicApiBaselineSnapshot.ps1. Remove the Trim() call only from the value
passed to $shipped.Contains; preserve trimming for the orphanedMarkers summary
if needed.
- Around line 65-66: Add a pair-level duplicate check around
Add-DuplicateEntries for the shipped and unshipped baselines, rejecting
identical entries that are not marked *REMOVED*. Preserve the valid case where a
shipped entry is paired with its *REMOVED* marker, while retaining the existing
per-file duplicate validation.

After applying the fix, consider running `coderabbit review --agent` for local
review. Visit https://docs.coderabbit.ai/cli.
🪄 Autofix

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: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 3cda1272-fee7-480e-b748-e2f41a122d6e

📥 Commits

Reviewing files that changed from the base of the PR and between f65c882 and beb88f6.

📒 Files selected for processing (66)
  • .github/workflows/dotnet.yml
  • .github/workflows/generate-cli-options.yml
  • scripts/Assert-PublicApiBaselines.ps1
  • scripts/GeneratedOptionsProvenance.ps1
  • scripts/PublicApiRemovedMarker.ps1
  • scripts/Test-AssertPublicApiBaselines.ps1
  • scripts/Test-PublicApiRemovedMarker.ps1
  • src/ModularPipelines.AmazonWebServices/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Ansible/PublicAPI.Unshipped.txt
  • src/ModularPipelines.ArgoCd/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Azure.Pipelines/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Azure/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Buildah/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Chocolatey/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Cmd/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Cosign/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Docker/PublicAPI.Unshipped.txt
  • src/ModularPipelines.DotNet/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Eksctl/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Email/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Flux/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Flyway/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Ftp/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Git/PublicAPI.Unshipped.txt
  • src/ModularPipelines.GitHub/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Go/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Google/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Grype/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Hadolint/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Helm/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Homebrew/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Java/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Jq/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Kind/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Kubernetes/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Liquibase/PublicAPI.Unshipped.txt
  • src/ModularPipelines.MicrosoftTeams/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Minikube/PublicAPI.Unshipped.txt
  • src/ModularPipelines.NerdbankGitVersioning/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Newman/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Node/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Packer/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Podman/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Pulumi/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Python/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Rust/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Shellcheck/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Skopeo/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Slack/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Snyk/PublicAPI.Unshipped.txt
  • src/ModularPipelines.SonarScanner/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Syft/PublicAPI.Unshipped.txt
  • src/ModularPipelines.TeamCity/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Terraform/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Testing/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Trivy/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Vault/PublicAPI.Unshipped.txt
  • src/ModularPipelines.WinGet/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Yarn/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Yq/PublicAPI.Unshipped.txt
  • src/ModularPipelines/PublicAPI.Unshipped.txt
  • tools/ModularPipelines.OptionsGenerator/scripts/Merge-PublicApiBaselineSnapshot.ps1
  • tools/ModularPipelines.OptionsGenerator/scripts/Test-MergePublicApiBaselineSnapshot.ps1
  • tools/ModularPipelines.OptionsGenerator/scripts/Test-SyncPublicApiBaselines.ps1
  • tools/ModularPipelines.OptionsGenerator/scripts/Test-WritePublicApiChangeSummary.ps1
  • tools/ModularPipelines.OptionsGenerator/scripts/Write-PublicApiChangeSummary.ps1
💤 Files with no reviewable changes (44)
  • src/ModularPipelines.Azure.Pipelines/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Jq/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Hadolint/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Java/PublicAPI.Unshipped.txt
  • src/ModularPipelines.MicrosoftTeams/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Shellcheck/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Yarn/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Ansible/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Slack/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Vault/PublicAPI.Unshipped.txt
  • src/ModularPipelines.TeamCity/PublicAPI.Unshipped.txt
  • src/ModularPipelines.WinGet/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Testing/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Ftp/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Snyk/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Syft/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Helm/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Liquibase/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Node/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Yq/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Python/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Grype/PublicAPI.Unshipped.txt
  • src/ModularPipelines.NerdbankGitVersioning/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Git/PublicAPI.Unshipped.txt
  • src/ModularPipelines.AmazonWebServices/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Email/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Eksctl/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Skopeo/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Go/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Packer/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Flyway/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Minikube/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Chocolatey/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Newman/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Cmd/PublicAPI.Unshipped.txt
  • src/ModularPipelines.DotNet/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Kubernetes/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Rust/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Buildah/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Terraform/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Trivy/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Cosign/PublicAPI.Unshipped.txt
  • src/ModularPipelines.SonarScanner/PublicAPI.Unshipped.txt
  • src/ModularPipelines.Kind/PublicAPI.Unshipped.txt
🚧 Files skipped from review as they are similar to previous changes (4)
  • tools/ModularPipelines.OptionsGenerator/scripts/Test-WritePublicApiChangeSummary.ps1
  • tools/ModularPipelines.OptionsGenerator/scripts/Test-MergePublicApiBaselineSnapshot.ps1
  • tools/ModularPipelines.OptionsGenerator/scripts/Test-SyncPublicApiBaselines.ps1
  • tools/ModularPipelines.OptionsGenerator/scripts/Write-PublicApiChangeSummary.ps1

Included review availability: Your plan provides up to 4 included reviews per hour; 0 remain after this review.

Comment thread scripts/Assert-PublicApiBaselines.ps1 Outdated
Comment thread scripts/Assert-PublicApiBaselines.ps1 Outdated
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Code review

Reviewed the shift to a PublicApiAnalyzers-lifecycle-aligned baseline sync (retain shipped entries, retire via REMOVED markers, only collapse on a future release "ship" step) plus the one-time migration that drops orphaned markers.

Findings: none blocking.

What I checked:

  • Merge-PublicApiBaselineSnapshot.ps1: shipped entries are now never dropped, and a REMOVED marker is only carried forward while its entry is still shipped and still absent from the current snapshot, so a marker can never be written or retained without a corresponding shipped line. This matches the PR description and eliminates the root cause (orphaned markers written same-commit as the shipped-line deletion).
  • scripts/PublicApiRemovedMarker.ps1 (new): small, well-scoped shared helper (prefix, test/get/new) that both the merge script and the new Assert-PublicApiBaselines.ps1 orphan check dot-source, avoiding duplicating the marker-prefix string across files.
  • scripts/Assert-PublicApiBaselines.ps1: new orphan-marker and duplicate-entry fast-fail checks are correct and reasonably efficient (duplicate check short-circuits via a HashSet count comparison before doing a line-by-line walk on 100k+ line shipped baselines).
  • Write-PublicApiChangeSummary.ps1: consistently treats a marker as retiring its shipped entry on both the "old" and "current" snapshot sides, so added/removed/signature-changed counts stay correct under the new shape.
  • Test coverage is thorough: Test-MergePublicApiBaselineSnapshot.ps1 adds an explicit "no confirmed removals" case (shipped untouched, no marker), keeps the removal/signature-change/idempotency cases, and drops the old case that fed an orphaned marker in. Test-AssertPublicApiBaselines.ps1 adds orphan and duplicate-entry failure cases. Test-PublicApiRemovedMarker.ps1 covers the marker helper edge cases (indentation, casing, empty, round-trip).
  • CI wiring (dotnet.yml, generate-cli-options.yml) correctly adds the new test scripts to the pipeline and adds PublicApiRemovedMarker.ps1 to the regen-trigger fingerprint list.
  • Spot-checked the one-time migration across several of the 54 rewritten PublicAPI.Unshipped.txt files (Docker, Terraform, Helm, AmazonWebServices, GitHub, Testing, Cmd, core ModularPipelines): in every file sampled, the only lines removed were REMOVED markers, and each removed marker had no corresponding entry left in PublicAPI.Shipped.txt, i.e. they were genuinely inert orphans rather than markers this PRs own logic would have kept. The ~92 markers retained in ModularPipelines and ModularPipelines.GitHub do still have live shipped entries.

Nothing here rises to a blocking issue: the design closes the exact bug described in #4638, the writer/reader logic is symmetric and covered by new tests, and the bulk migration matches what the new invariant would produce.

@thomhurst

Copy link
Copy Markdown
Owner Author

Addressed both CodeRabbit findings at exact head 58fc729885: markers now compare exactly against shipped entries (no trimming, matching the merge script), and a plain symbol present in both baselines is reported as an RS0025 duplicate while the shipped-entry-plus-marker pair remains valid. Both files are read once per project. Test-AssertPublicApiBaselines.ps1 covers both cases; the tree check still passes for 61 packages in ~3.5 s. @claude please re-review exact head 58fc729.

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Code review

Re-reviewed at HEAD 58fc729, the exact commit requested in the last author reply. I traced the full lifecycle change independently (not just diffed against the prior automated reviews on this thread) across Merge-PublicApiBaselineSnapshot.ps1, Assert-PublicApiBaselines.ps1, Write-PublicApiChangeSummary.ps1, PublicApiRemovedMarker.ps1, and their test files, plus spot-checked several of the 54 migrated PublicAPI.Unshipped.txt files.

Findings: none blocking.

What I verified:

  • Core invariant holds and can't regress silently. Merge-PublicApiBaselineSnapshot.ps1 now keeps every original shipped entry unconditionally, only adds a *REMOVED* marker when the RS0017 confirmed-removal gate says so, and only carries an existing marker forward while its target is both still shipped and still absent from the current snapshot. By construction the writer can't produce an orphan, and reintroducing a removed API correctly drops its marker on the next sync. Test-MergePublicApiBaselineSnapshot.ps1's idempotency + "no confirmed removals" + orphan-dropping (Api.Historical) cases confirm this by direct execution trace, not just by reading the assertions.
  • Both previously-flagged CodeRabbit findings on this exact head are actually fixed, not just claimed fixed. I read Assert-PublicApiBaselines.ps1 directly: line 84 compares $shipped.Contains((Get-RemovedMarkerEntry $line)) with no Trim(), matching the merge script's exact-match semantics (confirmed by the new trailing-whitespace-marker test case in Test-AssertPublicApiBaselines.ps1). Line 88's elseif ($shipped.Contains($line)) branch now catches a plain entry duplicated across both baseline files (the RS0025 shape) while still treating a shipped-entry-plus-its-marker pair as valid — also covered by a new test case (Api.Kept ... (also in PublicAPI.Shipped.txt)).
  • Single source of truth for the marker convention. scripts/PublicApiRemovedMarker.ps1 is dot-sourced by all three consumers; no script re-implements the *REMOVED* prefix or parsing rule anymore. It's wired into GeneratedOptionsProvenance.ps1/generate-cli-options.yml so the regen job re-triggers on changes to it, and it has its own edge-case test file.
  • Change-summary generation stays correct under the new shape. Write-PublicApiChangeSummary.ps1's Read-ActiveApi now nets out *REMOVED* markers against entries via ExceptWith on both the "old" and "current" snapshot sides, so added/removed/signature-change counts remain accurate for the generated PR body. Verified against Test-WritePublicApiChangeSummary.ps1's updated fixture (shipped entry retained, matching marker added, added/removed counts updated).
  • CI wiring is in place, not just added and forgotten: Test-PublicApiRemovedMarker.ps1 and Test-AssertPublicApiBaselines.ps1 (previously never run in CI) are now invoked in dotnet.yml ahead of Assert-PublicApiBaselines.ps1.
  • Migration spot-check. Sampled several of the 54 rewritten PublicAPI.Unshipped.txt files (Docker, AmazonWebServices, Ansible, core ModularPipelines) — every removed line in the diff is a *REMOVED* marker whose shipped counterpart is already gone, i.e. a genuine dead orphan under the new invariant, not something the new logic would have kept.

This closes out a long review thread (orphan-marker root cause → triplicated convention → leftover duplicate line → trim/duplicate-detection edge cases) where each round's finding was actually addressed rather than deferred, and the fix now matches its own stated goal: shipped entries are never dropped, and a marker without a shipped entry can no longer exist.

Follow the PublicApiAnalyzers lifecycle: a confirmed removal keeps its
PublicAPI.Shipped.txt line and is retired by a *REMOVED* marker in
PublicAPI.Unshipped.txt until a release ships the unshipped baseline.
Markers are only carried forward while their entry is still shipped, the
change summary subtracts markers from the active surface, and the
fast-fail baseline check rejects orphaned markers.

Delete the 19,617 markers whose shipped entry had already been removed
under the old convention so every baseline pair starts consistent.

Closes #4638

Claude-Session: https://claude.ai/code/session_01EHe9J8SumuZb2NBZxpGBzc
The marker prefix and its parse rule were implemented independently in
Assert-PublicApiBaselines, Merge-PublicApiBaselineSnapshot and
Write-PublicApiChangeSummary, so a convention change in one script could
silently reintroduce the orphaned-marker drift this branch fixes. Move the
prefix, the marker test, the entry parser and the marker constructor into
scripts/PublicApiRemovedMarker.ps1 and dot-source it from all three.

Fold the assert script's missing-baseline and orphaned-marker loops into a
single pass over the package projects so each project's baseline paths are
derived once and future per-project checks share the loop.

Cover the helper with scripts/Test-PublicApiRemovedMarker.ps1 and run it,
together with the existing Assert-PublicApiBaselines test that CI never
executed, before the baseline coverage check in dotnet.yml.

Refs #4638

Claude-Session: https://claude.ai/code/session_01PkLNTUfwGXjqXZrYrHaDGC
scripts/PublicApiRemovedMarker.ps1 now shapes generated baseline output,
so it joins the generator source fingerprint and the regeneration
workflow's push paths.

Claude-Session: https://claude.ai/code/session_01EHe9J8SumuZb2NBZxpGBzc
Drop the duplicated DequeueModuleAsync marker from the core unshipped
baseline and make the fast-fail check report any entry listed twice in
a PublicAPI file, so a migration or hand edit cannot reintroduce RS0024.

Claude-Session: https://claude.ai/code/session_01EHe9J8SumuZb2NBZxpGBzc
The fast-fail check now matches *REMOVED* markers against shipped
entries exactly, as the merge script does, and reports a plain symbol
that appears in both baselines (the RS0025 shape).

Claude-Session: https://claude.ai/code/session_01EHe9J8SumuZb2NBZxpGBzc
The console error view wraps long messages on Linux, so the cross-file
duplicate and whitespace-marker cases read the thrown message directly.

Claude-Session: https://claude.ai/code/session_01EHe9J8SumuZb2NBZxpGBzc
@thomhurst
thomhurst force-pushed the issue-4638-publicapi-removed-markers branch from 58fc729 to 5dac539 Compare September 6, 2026 01:40
@thomhurst

Copy link
Copy Markdown
Owner Author

Fixed the Fast fail failure at exact head 5dac5398f4 (also rebased onto current main after #4656, conflict-free). Test-AssertPublicApiBaselines.ps1 matched the assert script's message against the console-rendered output, which PowerShell wraps at 80 columns with a gutter on Linux, so the long cross-file-duplicate message never matched there. The two long-message cases now run the script in-process and inspect the thrown message. No product change; all script tests and the 61-package tree check pass locally.

@thomhurst

Copy link
Copy Markdown
Owner Author

CI on 5dac539 failed in the Test public API baseline checks step after both scripts printed success: the last negative case in Test-AssertPublicApiBaselines.ps1 runs the gate in a child pwsh that is expected to fail, leaving \0 at 1, which the Actions pwsh shell reports as the step's exit code. 89864c6 ends the script with an explicit exit 0 (emulated the wrapper locally: exit 0). It also picks up the review's whitespace observation: Test-ApiEntry now treats whitespace-only lines as blank like Read-Baseline in the merge script, with a test case. Real gate on this checkout: 61 package projects verified.

@claude please re-review exact head 89864c6.

Resolves the brew (#4621) and pulumi (#4637) baseline conflicts by taking
the regenerated files from main and applying the same one-time migration:
*REMOVED* markers whose shipped entry is already gone are dropped, so the
fast-fail orphan check passes against the current baselines.

Claude-Session: https://claude.ai/code/session_01PkLNTUfwGXjqXZrYrHaDGC
@thomhurst

Copy link
Copy Markdown
Owner Author

Merged main (brew #4621 and pulumi #4637 regenerations conflicted on their PublicAPI.Unshipped.txt). Resolution takes main's regenerated files and applies the PR's one-time migration rule to them: markers whose shipped entry is already gone are dropped, so the new orphan check passes. Assert-PublicApiBaselines.ps1 on the merged tree: 61 package projects verified; the baseline test step exits 0.

@claude please re-review exact head 1449139.

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Review: Public API baseline validation - shared REMOVED marker helper

This follow-up commit addresses both issues raised in the previous review:

  1. Duplicate marker in the migrated baseline - src/ModularPipelines/PublicAPI.Unshipped.txt now has a single REMOVED...DequeueModuleAsync(...) entry (line 20); the duplicate at the old line 32 is gone.
  2. REMOVED convention triplication - the new scripts/PublicApiRemovedMarker.ps1 (Get-RemovedMarkerPrefix/Test-RemovedMarker/Get-RemovedMarkerEntry/New-RemovedMarker) is now dot-sourced by scripts/Assert-PublicApiBaselines.ps1, tools/ModularPipelines.OptionsGenerator/scripts/Merge-PublicApiBaselineSnapshot.ps1, and Write-PublicApiChangeSummary.ps1, and is covered by its own Test-PublicApiRemovedMarker.ps1. This is exactly the single-source-of-truth fix that was requested - good catch on wiring it into GeneratedOptionsProvenance.ps1's watch list too, so the CLI-options regen workflow re-triggers if the helper changes.

Two smaller design issues remain in scripts/Assert-PublicApiBaselines.ps1, worth a follow-up (neither blocks this specific fix, but both reduce the value of the new checker as a diagnostic tool):

  1. Sequential if { throw } blocks hide all but the first category of failure

Assert-PublicApiBaselines.ps1:95-108 checks missing baselines, orphaned markers, and duplicate entries as three separate gated throws. If a baseline directory has, say, both an orphaned marker and a duplicate entry at once (a plausible outcome of a bad manual edit or a merge conflict), only the orphaned-marker error is ever reported - the duplicate-entry problem stays invisible until the first issue is fixed and CI reruns. Since all three lists (missingBaselines, orphanedMarkers, duplicateEntries) are already being built independently regardless of which condition trips first, this could report all populated categories in a single combined message instead of stopping at the first one - same detection cost, but a contributor fixing baselines sees every problem in one CI run instead of drip-feeding through re-runs.

  1. Overlapping/duplicate error output for one underlying data problem

Add-DuplicateEntries (line 38) flags intra-file duplicates, and the main loop's elseif (shipped.Contains(line)) branch (line 89-91) separately flags any plain (non-marker) unshipped entry that's also in PublicAPI.Shipped.txt. If an entry is both duplicated within PublicAPI.Unshipped.txt and also present in PublicAPI.Shipped.txt, it gets reported once by Add-DuplicateEntries and again by the main loop for every occurrence - 3 overlapping lines in the thrown error for what's really one data problem. Deduplicating the unshipped lines once up front (or having the main loop only inspect distinct entries) before running the cross-file check would avoid the redundant noise and make the failure easier to triage at a glance.

Minor, no action needed: Test-AssertPublicApiBaselines.ps1:119 ends with an unconditional exit 0 to reset LASTEXITCODE after intentionally-failing pwsh -File sub-invocations. It's only safe today because it's the last statement in the last script of the dotnet.yml run block (Test-PublicApiRemovedMarker.ps1 then Test-AssertPublicApiBaselines.ps1) - exit inside a non-dot-sourced script invocation terminates the whole pwsh session, not just the local script scope. If a later commit appends another step to that block (mirroring the existing multi-script chains elsewhere in the repo, e.g. generate-cli-options.yml), it would silently never execute. Setting LASTEXITCODE = 0 after the negative-test invocations instead of calling exit 0 would get the same result without depending on this script always being last.

Assert-PublicApiBaselines threw on the first populated category, so a
tree with both an orphaned marker and a duplicate entry only showed the
first until CI reran. Collect all categories into one message. Inspect
each distinct unshipped entry once for the cross-file check so an entry
that repeats and is also shipped yields one intra-file and one cross-file
finding instead of one per occurrence. The test script now resets
LASTEXITCODE instead of exiting, so later scripts in the same step still
run.

Refs #4638

Claude-Session: https://claude.ai/code/session_01PkLNTUfwGXjqXZrYrHaDGC
@thomhurst

Copy link
Copy Markdown
Owner Author

Took all three follow-ups in 592e246: Assert-PublicApiBaselines.ps1 now reports every populated category (missing baselines, orphaned markers, duplicates) in one thrown message; the cross-file check inspects each distinct unshipped entry once, so a repeated-and-shipped entry yields one intra-file plus one cross-file finding rather than one per occurrence; and the test script resets LASTEXITCODE instead of exit 0 so later scripts in the same step still run. Two new negative cases cover the combined report and the de-duplicated finding count. Emulated step exit 0 with a trailing statement executing; real gate 61 projects.

@claude please re-review exact head 592e246.

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Code review

Re-reviewed at HEAD 592e246. This is the same lifecycle rework tracked through the whole thread — shipped entries are retained, a `REMOVED` marker only exists while its shipped entry is still present and still absent from the current snapshot, and the writer can no longer produce an orphan by construction.

This round's commit is scoped to the three follow-ups from the last BLOCKING review (head 1449139), all of which I verified directly against the current script content, not just the author's summary:

  1. Combined failure reportingscripts/Assert-PublicApiBaselines.ps1 now builds missingBaselines/orphanedMarkers/duplicateEntries independently and throws one message containing every populated category, instead of stopping at the first. Confirmed by the new "combined" test case in Test-AssertPublicApiBaselines.ps1 asserting both 'markers without a shipped entry' and 'more than once' appear together.
  2. De-duplicated cross-file findings — the orphan/duplicate loop now tracks an $inspected HashSet per unshipped file so a repeated-and-shipped entry produces exactly one intra-file duplicate finding (from Add-DuplicateEntries) plus one cross-file finding, not one per occurrence. The new test asserts the count is exactly 2, which matches the implementation.
  3. LASTEXITCODE reset instead of exit 0Test-AssertPublicApiBaselines.ps1 now sets $global:LASTEXITCODE = 0 after the intentionally-failing child pwsh invocations rather than calling exit, so a later step appended after this script in the same dotnet.yml block would still run. This removes the "only safe because it's last" fragility flagged previously.

I also re-traced the core algorithm independently on this head:

  • Merge-PublicApiBaselineSnapshot.ps1: shipped entries are never dropped; a marker is only added when RS0017's confirmed-removal gate says so, and an existing marker is only carried forward while $shippedApis.Contains($removedApi) -and -not $currentApis.Contains($removedApi) — so a reintroduced API correctly loses its marker on the next sync.
  • Write-PublicApiChangeSummary.ps1's Read-ActiveApi nets markers against entries via ExceptWith on both the old and current snapshot sides, so added/removed/signature-change counts stay correct under the retained-shipped-entry shape.
  • scripts/PublicApiRemovedMarker.ps1 remains the single source of truth for the prefix/parse rule, dot-sourced by all three consumers plus wired into GeneratedOptionsProvenance.ps1/generate-cli-options.yml so the regen job re-triggers on changes to it.
  • Spot-checked src/ModularPipelines/PublicAPI.Unshipped.txt (the file with the duplicate flagged in earlier rounds) — no duplicate lines remain.

All issues raised across this review thread (orphan-marker root cause, marker-convention triplication, the leftover duplicate migrated line, marker/blank-line trim consistency, combined-report ordering, duplicate-finding noise, and the exit 0 fragility) have now been addressed and verified fixed at this exact head. No new issues found.

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Review: Keep shipped public API entries when recording removals

This is a solid, well-targeted bug fix. The core problem it solves: Merge-PublicApiBaselineSnapshot.ps1 was previously dropping an entry from PublicAPI.Shipped.txt the moment a *REMOVED* marker was recorded for it, instead of waiting for an actual release to ship the unshipped baseline. That left *REMOVED* markers in PublicAPI.Unshipped.txt with nothing left in Shipped.txt to retire, which is exactly the "orphaned marker" condition the new Assert-PublicApiBaselines.ps1 checks catch. The ~22k line diff is almost entirely mechanical cleanup of those now-invalid orphaned markers across every PublicAPI.Unshipped.txt file — not hand-edited logic.

Things I verified:

  • scripts/PublicApiRemovedMarker.ps1 (new) centralizes the *REMOVED* prefix convention, and every call site that previously hardcoded the prefix (Assert-PublicApiBaselines.ps1, Merge-PublicApiBaselineSnapshot.ps1, Write-PublicApiChangeSummary.ps1) now dot-sources it consistently.
  • The merge script fix (Merge-PublicApiBaselineSnapshot.ps1) now keeps originalShipped entries intact regardless of confirmedRemovedApis, and only adds a marker to unshippedApis when the entry is still in shippedApis and absent from currentApis — this matches the stated invariant ("a marker only means something while its entry is still shipped and still absent") and is covered by the updated test fixtures.
  • Assert-PublicApiBaselines.ps1's new checks (orphaned markers, cross-file/intra-file duplicates) are well covered by Test-AssertPublicApiBaselines.ps1, including the "combined failure reports every category" and "repeated shipped entry counted once per occurrence" cases.
  • The $global:LASTEXITCODE = 0 reset in Test-AssertPublicApiBaselines.ps1 is a legitimate fix — without it, the leftover exit code from the negative-path sub-invocations would make the CI step look like it failed even though the assertions passed.
  • $a, $b | Where-Object {...} in Assert-PublicApiBaselines.ps1 parses correctly in PowerShell (comma binds tighter than pipe), so this isn't a bug.

One gap worth considering (minor, not a regression from this PR's main fix):

scripts/Assert-PublicApiBaselines.ps1's orphaned-marker check only inspects PublicAPI.Unshipped.txt, never PublicAPI.Shipped.txt. If a *REMOVED*-prefixed line ever ends up in Shipped.txt (e.g. a bad manual edit or merge conflict resolution), none of the three checks — missing baseline, orphaned marker, duplicate entry — catch it, since the orphaned-marker loop only walks $unshippedLines. The Roslyn PublicApiAnalyzer would presumably still fail the real build on that stray line, but with a confusing analyzer diagnostic instead of this script's actionable message — which is precisely the failure mode this script exists to prevent. Given the normal automated flow (Merge-PublicApiBaselineSnapshot.ps1) never writes a marker into Shipped.txt, this can only happen through manual error, so it's low-probability — but since the script's whole purpose is defending against exactly this class of mistake, it may be worth a follow-up check (e.g. flag any Shipped.txt line matching the marker prefix) rather than leaving it to the analyzer to surface later.

Nice-to-have, not blocking.

The orphaned-marker check only walked PublicAPI.Unshipped.txt, so a
marker pasted into the shipped baseline by a bad merge went unnoticed by
every category. Report it alongside the orphaned markers and cover it in
the test script.

Refs #4638

Claude-Session: https://claude.ai/code/session_01PkLNTUfwGXjqXZrYrHaDGC
@thomhurst

Copy link
Copy Markdown
Owner Author

Took the remaining nice-to-have in b2867d4: a *REMOVED* marker found in PublicAPI.Shipped.txt is now reported with the orphaned markers (... (marker in PublicAPI.Shipped.txt)), with a test-script case. Emulated step exit 0; real gate 61 projects.

@claude please re-review exact head b2867d4.

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Review

I traced the full lifecycle change independently: Merge-PublicApiBaselineSnapshot.ps1 (shipped entries are now never dropped; a *REMOVED* marker is only added when RS0017 confirms removal, and an existing marker is only carried forward while its entry is still shipped and still absent from the current snapshot — so the writer cannot produce an orphan by construction), Assert-PublicApiBaselines.ps1 (now reports missing baselines, orphaned markers — including a marker that strayed into PublicAPI.Shipped.txt — and duplicate entries, all in one pass/one thrown message), Write-PublicApiChangeSummary.ps1 (retired entries are excluded from the active-surface diff via the shared marker helper), and the new scripts/PublicApiRemovedMarker.ps1 (single source of truth for the *REMOVED* prefix/parse rule, correctly dot-sourced with the right relative path from both scripts/ and tools/ModularPipelines.OptionsGenerator/scripts/).

I hand-verified the merge algorithm against Test-MergePublicApiBaselineSnapshot.ps1's fixtures (confirmed removal, signature change, orphan-marker drop, reintroduced API, empty-removals, idempotency) and the numbers check out. I also spot-checked the migrated PublicAPI.Unshipped.txt for ModularPipelines and ModularPipelines.GitHub (the two packages that retain markers) — marker counts (89 + 2) are in the right ballpark versus the ~92 claimed in the description, and the CI wiring (Test-PublicApiRemovedMarker.ps1 + Test-AssertPublicApiBaselines.ps1 run before the real Assert-PublicApiBaselines.ps1 gate; PublicApiRemovedMarker.ps1 added to the provenance fingerprint and the generate-cli-options.yml path filter) is consistent.

This PR went through many rounds of automated review already (orphan/duplicate detection, shared-helper extraction, exit-code handling in the CI step, the shipped-file stray-marker case) and every prior finding is resolved in the current head. I don't have anything new to add — the design is sound: retaining shipped entries and gating marker lifetime on "still shipped and still absent" is the right fix for the root cause (the old code deleted the shipped line in the same commit that wrote the marker, orphaning it immediately), and the one-time migration is scoped correctly (drop only markers whose shipped entry is already gone, keep the rest).

No blocking issues found.

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Review: Keep shipped public API entries when recording removals

Re-reviewed at HEAD b2867d4. All issues raised across this review thread have now been addressed and verified fixed at this head:

  • Duplicate marker in the migrated baselinesrc/ModularPipelines/PublicAPI.Unshipped.txt no longer has the duplicate *REMOVED*...DequeueModuleAsync(IReadOnlySet<string!>...) line; only the correct entry remains.
  • *REMOVED* prefix/parsing triplication — now centralized in scripts/PublicApiRemovedMarker.ps1 (Get-RemovedMarkerPrefix, Test-RemovedMarker, Get-RemovedMarkerEntry, New-RemovedMarker), dot-sourced consistently by Assert-PublicApiBaselines.ps1, Merge-PublicApiBaselineSnapshot.ps1, and Write-PublicApiChangeSummary.ps1. It's also wired into GeneratedOptionsProvenance.ps1 and generate-cli-options.yml's trigger paths, so a future convention change correctly re-triggers regeneration.
  • Orphaned-marker check only covering PublicAPI.Unshipped.txtAssert-PublicApiBaselines.ps1 now also scans PublicAPI.Shipped.txt for a stray *REMOVED*-prefixed line (lines 74-81) and reports it as an orphaned marker, with a dedicated regression test (Test-AssertPublicApiBaselines.ps1, the "stray marker in PublicAPI.Shipped.txt" case).
  • Combined-report ordering, de-duplicated cross-file findings, and the $global:LASTEXITCODE = 0 fix from earlier rounds remain in place and are still covered by their respective tests.

I independently re-traced the core algorithm:

  • Merge-PublicApiBaselineSnapshot.ps1 never drops a shipped entry; a marker is only added when RS0017's confirmed-removal set says so, and an existing marker only carries forward while $shippedApis.Contains($removedApi) -and -not $currentApis.Contains($removedApi) — so a reintroduced API correctly loses its marker on the next sync.
  • Write-PublicApiChangeSummary.ps1's Read-ActiveApi nets markers against entries via ExceptWith, so added/removed/signature-change counts stay correct under the retained-shipped-entry shape.

No new issues found. This is a well-tested, mechanically-verified fix with no outstanding items from prior rounds.

Keeps both sides of Test-MergePublicApiBaselineSnapshot.ps1: the
no-confirmed-removals case from this branch runs on the original inputs
first, then the empty-input cases from #4609.

Claude-Session: https://claude.ai/code/session_01PkLNTUfwGXjqXZrYrHaDGC
@thomhurst

Copy link
Copy Markdown
Owner Author

Merged main after #4609 landed (conflict in Test-MergePublicApiBaselineSnapshot.ps1: both sides added cases at the end; kept both, the no-confirmed-removals case first, then #4609's empty-input cases). All five baseline test scripts and the real gate pass locally.

@claude please re-review exact head 1c9464c.

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Review: Keep shipped public API entries when recording removals

Re-reviewed at HEAD 1c9464c. This head only adds one commit since the last CLEAR review (b2867d485a): a merge of main after #4609 landed, which conflicted in Test-MergePublicApiBaselineSnapshot.ps1 (both branches appended a new case at the end of the same test file).

I read the resulting test file directly rather than trusting the author's description of the resolution:

  • This PR's own "no confirmed removals" case (shipped baseline untouched, no marker written) is kept intact and runs first.
  • Handle empty public API baseline snapshots #4609's "truly empty inputs" case (header-only files still produce empty baselines, including for output files that don't exist yet) is kept intact and runs after it, using its own file handles ($emptyRemovals/$noRemovalShipped/$noRemovalUnshipped vs. the pre-existing $originalShipped/$originalUnshipped/$shippedOutput), so the two additions don't clobber each other's fixtures.
  • The final Write-Output success message was updated to mention "empty removals" alongside the pre-existing "empty inputs," matching the added case.

I also re-confirmed the merge conflict didn't leak into product code: Merge-PublicApiBaselineSnapshot.ps1 and scripts/Assert-PublicApiBaselines.ps1 are unchanged from the algorithm already verified across many prior review rounds on this thread (shipped entries are never dropped; a *REMOVED* marker is only added when RS0017 confirms removal and only carried forward while its entry is still shipped and still absent; the assert script reports missing baselines, orphaned markers — including strays in PublicAPI.Shipped.txt — and duplicate entries all in one pass).

Every issue raised across this long review thread (orphaned-marker root cause, marker-convention triplication, the leftover duplicate migrated line, marker/blank-line trim consistency, combined-report ordering, duplicate-finding noise, exit 0 fragility, and now this merge-conflict resolution) has been addressed and holds up at this exact head.

No blocking issues found.

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Review: Keep shipped public API entries when recording removals

Re-reviewed at HEAD 1c9464c. This is the same commit already given a CLEAR verdict in the previous automated review round, and my independent pass reaches the same conclusion.

I traced the core lifecycle change directly rather than relying on prior summaries:

  • Merge-PublicApiBaselineSnapshot.ps1: every original shipped entry is retained unconditionally; a *REMOVED* marker is only added when the RS0017 confirmed-removal set says so, and an existing marker is only carried forward while $shippedApis.Contains($removedApi) -and -not $currentApis.Contains($removedApi). By construction the writer cannot produce an orphan, and a reintroduced API correctly drops its marker on the next sync.
  • scripts/Assert-PublicApiBaselines.ps1: single pass per project builds missing-baseline, orphaned-marker (including a marker stray in PublicAPI.Shipped.txt), and duplicate-entry findings, and reports every populated category in one thrown message. The intra-file/cross-file duplicate checks dedupe correctly via $inspected, so a repeated-and-shipped entry surfaces as one finding per check rather than once per occurrence.
  • Write-PublicApiChangeSummary.ps1: Read-ActiveApi nets *REMOVED* markers against entries via ExceptWith on both snapshot sides, keeping added/removed/signature-change counts correct under the retained-shipped-entry shape.
  • scripts/PublicApiRemovedMarker.ps1 remains the single source of truth for the prefix/parse rule, dot-sourced by all three consumers with its own test coverage, and wired into GeneratedOptionsProvenance.ps1/generate-cli-options.yml so the regen job re-triggers on changes to it.
  • The only change since the last CLEAR review is a main merge that resolved a conflict in Test-MergePublicApiBaselineSnapshot.ps1 by keeping both this PR's "no confirmed removals" case and Handle empty public API baseline snapshots #4609's "truly empty inputs" case with separate fixture variables — I read the merged test file directly and confirmed neither case clobbers the other, and no product code changed in that merge.
  • Spot-checked several of the mass-migrated PublicAPI.Unshipped.txt files (Google, Docker, core ModularPipelines) — every removed line is a *REMOVED* marker whose shipped counterpart is already gone, consistent with the stated one-time migration rule.

Every issue raised across this review thread (orphaned-marker root cause, marker-convention triplication, the leftover duplicate migrated line, marker/blank-line trim consistency, combined-report ordering, duplicate-finding noise, exit 0 fragility, and the shipped-file stray-marker gap) has been addressed and holds up at this exact head. No new issues found.

Resolves the Flux PublicAPI.Unshipped.txt conflict the way the earlier sync did: main's Shipped.txt stays as merged, the markers orphaned by the pre-#4644 flux update are dropped, and the three new-ordinal FluxBootstrapGitlabVisibility entries are kept.

Claude-Session: https://claude.ai/code/session_01EHe9J8SumuZb2NBZxpGBzc
Resolves the go (#4686) baseline conflict the same way as brew, pulumi
and flux: take the regenerated file from main and drop the *REMOVED*
markers whose shipped entry is already gone.

Claude-Session: https://claude.ai/code/session_01PkLNTUfwGXjqXZrYrHaDGC
@thomhurst

Copy link
Copy Markdown
Owner Author

Merged main again (go #4686 regeneration conflicted on its PublicAPI.Unshipped.txt; resolved like brew/pulumi/flux: main's file minus markers whose shipped entry is gone). Gate 61 projects; baseline test step exit 0.

@claude please re-review exact head 1fc04bc.

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Code review

Reviewed head 1fc04bc (PR #4644). This is a well-scoped, carefully-tested fix for the orphaned-marker bug described in #4638. I focused on the 12 substantive script/workflow files; the other 54 changed files are the described mechanical migration (deleting *REMOVED* markers whose shipped entry is already gone).

What the change does, and why it's sound:

  • Merge-PublicApiBaselineSnapshot.ps1 now keeps every original shipped entry ($shippedApis is built directly from $originalShipped instead of dropping confirmed removals), so a *REMOVED* marker it writes always has a live shipped counterpart. Carrying an existing marker forward now additionally requires $shippedApis.Contains($removedApi), which is the self-healing piece of the fix — a marker that was never (or no longer) shipped gets dropped instead of perpetuated.
  • The new shared scripts/PublicApiRemovedMarker.ps1 helper (prefix/Test-RemovedMarker/Get-RemovedMarkerEntry/New-RemovedMarker) is dot-sourced consistently from Assert-PublicApiBaselines.ps1, Merge-PublicApiBaselineSnapshot.ps1, and Write-PublicApiChangeSummary.ps1, so the marker convention lives in one place as intended.
  • Assert-PublicApiBaselines.ps1's new orphan/duplicate/stray-marker checks and Write-PublicApiChangeSummary.ps1's Read-ActiveApi (excluding retired entries via ExceptWith) correctly implement the new "shipped entry + marker" pair semantics.
  • I spot-checked the mechanical migration against the actual repo state rather than trusting the diff alone: e.g. in src/ModularPipelines.Go/PublicAPI.Unshipped.txt the five deleted markers have zero matches in that package's PublicAPI.Shipped.txt (correctly orphaned), while in src/ModularPipelines/PublicAPI.Unshipped.txt, *REMOVED*ModularPipelines.Attributes.CommandLinePhase.EndOfOptions is correctly retained because its shipped entry still exists, whereas the deleted *REMOVED*ModularPipelines.Attributes.DependsOnAllModulesInheritingFromAttribute has no shipped entry. Both match the new invariant.
  • Both actionable findings from CodeRabbit's earlier review round on this PR (the .Trim() mismatch between the marker lookup and Merge-PublicApiBaselineSnapshot.ps1's comparison, and the missing cross-file duplicate check for a plain entry that's also shipped) are already addressed in the current head — there's no Trim() call left in Assert-PublicApiBaselines.ps1, and the elseif ($shipped.Contains($line)) branch now reports that duplicate case.
  • Test coverage is thorough: the new Test-AssertPublicApiBaselines.ps1 cases exercise orphaned markers, duplicates (intra-file, cross-file, and combined), whitespace-sensitive marker comparison, and a marker stray in PublicAPI.Shipped.txt; Test-MergePublicApiBaselineSnapshot.ps1 adds a no-confirmed-removals case proving the shipped baseline is left untouched.

No actionable issues found.

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Correction: the verdict marker was omitted from my review comment above. Adding it here.

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Code review

Reviewed head 1fc04bc. This is a well-scoped, thoroughly-tested fix for the orphaned-*REMOVED*-marker bug described in #4638. I traced the core algorithm independently and spot-checked the mechanical migration against the actual repo state rather than relying on the diff or the PR description alone.

Core logic (Merge-PublicApiBaselineSnapshot.ps1, Assert-PublicApiBaselines.ps1, Write-PublicApiChangeSummary.ps1, scripts/PublicApiRemovedMarker.ps1):

  • Merge-PublicApiBaselineSnapshot.ps1 now builds $shippedApis directly from $originalShipped (no entries are ever dropped there), and only carries an existing *REMOVED* marker forward when $shippedApis.Contains($removedApi) -and -not $currentApis.Contains($removedApi). That guard is the actual fix: a marker can no longer be written or perpetuated without a live shipped counterpart, and a reintroduced API correctly loses its stale marker on the next sync. Verified against Test-MergePublicApiBaselineSnapshot.ps1's removal/signature-change/no-confirmed-removals/idempotency fixtures.
  • scripts/PublicApiRemovedMarker.ps1 is a genuine single source of truth for the *REMOVED* prefix/parse rule, correctly dot-sourced (with correct relative paths) from all three consumers, and wired into GeneratedOptionsProvenance.ps1 / generate-cli-options.yml so the regen job re-triggers on changes to it.
  • Assert-PublicApiBaselines.ps1's combined orphan/duplicate/stray-marker checks report every populated category in one pass, dedupe cross-file findings correctly via $inspected, and compare markers without trimming (matching the merge script's exact-match semantics).
  • Write-PublicApiChangeSummary.ps1's Read-ActiveApi nets *REMOVED* markers against entries via ExceptWith, keeping added/removed/signature-change counts correct.
  • The $a, $b | Where-Object {...} construct in Assert-PublicApiBaselines.ps1 parses correctly in PowerShell (comma binds tighter than pipe, so both paths get filtered) — not a bug, despite looking like one at a glance.

Migration spot-checks: In src/ModularPipelines.Go/PublicAPI.Unshipped.txt and src/ModularPipelines/PublicAPI.Unshipped.txt, I confirmed by grepping the actual PublicAPI.Shipped.txt files that every deleted marker genuinely had no shipped counterpart (e.g. DependsOnAllModulesInheritingFromAttribute), while retained markers do (e.g. CommandLinePhase.EndOfOptions). This matches the new invariant exactly.

Test coverage is thorough: Test-PublicApiRemovedMarker.ps1 covers the helper's edge cases; Test-AssertPublicApiBaselines.ps1 covers orphaned markers, intra-file/cross-file/combined duplicates, whitespace-sensitive comparison, and shipped-file stray markers; Test-MergePublicApiBaselineSnapshot.ps1 covers the no-confirmed-removals and reintroduced-API cases. CI wiring (dotnet.yml) runs both new test scripts ahead of the real Assert-PublicApiBaselines.ps1 gate.

This closes out a long review thread where every prior finding (marker triplication, a leftover duplicate migrated line, trim/duplicate-detection inconsistencies, exit 0 fragility, the shipped-file stray-marker gap) was substantively addressed rather than deferred. I don't have anything new to add.

No actionable issues found.

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.

PublicAPI baseline sync orphans *REMOVED* markers by deleting shipped entries immediately

1 participant