Skip to content

A satellite PR compiles against the set its main last PASSED — so when main is red because the set moved, the fix cannot be proven and may not name the new API #4348

Description

@rbuergi

A satellite's PR lane compiles against the newest sealed set its own main has PASSED, not the newest sealed set. That is deliberate and right in the normal case — pull requests follow main — but it produces a deadlock in exactly the case where a fix is most urgent: when main is red because the set moved.

Measured today, on the PR fixing the fleet's publication outage

MeshWeaver.Plugins#1873 exists to fix two suites that core changes broke on arrival in set 3.0.0-ci.8596. Its own platform job says:

skip main-cd #8596 …: sealed, but this repo's `main` has not passed on it yet
                      (main's newest passed set is core CD #8587)
set=3.0.0-ci.8587

The resolver is behaving exactly as documented (resolve-platform.py:1016-1019). The consequences, all measured on that PR:

  1. The PR lane cannot exercise the regression the PR exists to fix. It builds and runs against the set before the break, so it can go green having proven nothing about the set main will actually use.
  2. It is worse than uninformative — it is hostile to the clean fix. The first attempt used the API the breaking core change introduced (PackageInstaller.SetUpdatePolicy, which is what the catalog card itself calls). The PR went red: CS0103 PackageUpdatePolicy, CS0117 SetUpdatePolicy, CS1061 EffectiveUpdatePolicy — the set it compiles against has none of those symbols.
  3. So a fix for a set-move regression must compile on the OLD set AND pass on the NEW one, and may name no symbol the new set introduced.

For #1872 that was resolvable — the fix builds the record's legacy shape as JSON (content["autoUpdate"] = true; content.Remove("updatePolicy")), which names nothing new and is the more honest test, since that shape is exactly the population production holds: every record installed before the change. CI offers one oracle, so both were taken locally against two core worktrees — set 8587: 9/9; core main: 9/9, PluginCatalog.Test 734/734, FutuReAnalysisTest 46/46.

But that was luck of this particular break. A set-move regression whose only honest fix requires the new API has no green path at all: the PR cannot compile, and main cannot go green until the PR merges.

Why the current design is still right

Worth stating so the fix is not a loosening. The ceiling exists because a PR that silently resolved a newer set than main has passed would be testing against bytes main has never validated — and #1826/#4265 record what that cost. refuses rather than falling back is the correct default, and a fallback that quietly took the newest sealed set would re-open exactly that hole.

The question, which is not an agent's to answer

Should the PR lane offer an explicit, per-PR opt-in to the newest sealed set when main is red on it — something a human types, that is visible in the run, and that the PR body must justify — rather than a silent fallback?

Arguments the decision has to weigh:

  • For: without it, every platform-caused satellite red has this chicken-and-egg, and the only escape is a fix expressible in the old vocabulary. That is a constraint on the shape of the fix, imposed by the lane rather than by the problem.
  • Against: an opt-in is a skip-trapdoor wearing a justification, and the fleet's own rule is that a gate must never let the caller decide whether it applies. It would also be reached for under exactly the pressure that makes people careless — a red main.
  • A third option: make the deadlock visible rather than escapable — have the resolver say, when it holds a PR back on a set main is red on, that this PR may be unable to prove its own fix, and name the set it would have used. The lane stays honest and the author is told what they cannot measure.

Recorded as item 18 in MeshWeaver.Plugins/Hosting/PortalHostSuites.md with the two-command attribution recipe. Filed here because it is a fleet property, not a Plugins one — .Education, .Reinsurance, .SocialMedia, .Manufacturing and .Crm all call the same lane.

Related: #4299 (the review gap that let the breaking change through unread), Plugins#1872 (the outage), #1826 / #4265 (why the ceiling exists), break-shape 7 in CrossRepoPairGate.md (why no surface gate saw it).


🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions