One Promotion Gate

Three policies, one design (register: Policy Not Prose):

policy the rule
core-merge-never-blocked A core pull request merges on core's OWN required checks. No required context, gate or queue step waits on MeshWeaver.Plugins or any other repository.
build-latest-green Every CI consumer — Plugins pull requests, Plugins main, the image build — takes the NEWEST GREEN core main build. A red core main falls back to the last green one, never forward into red. No pin to move for ordinary changes; the freeze variable MW_PLATFORM_REF stays for incidents.
one-promotion-gate The fleet rolls only to an ARMED set, and a set is armed only when MeshWeaver.Plugins' dependent suites passed against exactly the pair it was built from. That is the one place a cross-repository verdict decides anything.

Why — measured on 2026-09-27

The flow now

core PR ──(core's own checks)──► core main ──► Build and Test ──► main-cd
                                                                   │
Plugins push to main ──(image-relevant?)──► main-cd rebuild ───────┤  coalesced: one run in flight,
                                                                   │  the newest pending supersedes
                                                                   ▼
            gate: core main HEAD + Plugins main HEAD (at gate time) = the PAIR <core7>-p<plugins7>
                                                                   │
                         build ─► promote (phases A+B: identity + pair tag, CI pointers)
                                  + promotion record (artifact)    │
                                                                   ├─► verify, platform bake,
                                                                   │   Plugins seal, satellites …
                                                                   │   (CI reads the set NOW)
                                                                   ▼
   Plugins promotion-candidate.yml (poll) ──► core-candidate.yml: dependent suites against the pair
                                                                   │ verdict at
                                                                   │ refs/core-candidate/pair-<core7>-p<plugins7>
                                                                   ▼
                  main-cd `arm` (every run) ──► phase C memex-portal-ai:<version>
                                                phase D <line>-latest pointers
                                                notify-platform-update (the release event)
                                                                   │
                                                                   ▼
                                     the fleet: Continuous on 3.0.0-ci* — rolls to ARMED tags only

Never block a core merge

Consolidate test results no longer needs preflight, dependent-suites-dispatch or dependent-suites, and no step of it reads them. The dependent-suites run on a pull request is advisory: requested by the dependent-suites label or by a declared Plugins counterpart (Pairs-with: Systemorph/MeshWeaver.Plugins#<n>, see Paired Change Sets), reported on the pull request, and blocking nothing. Measure the required set through the ruleset endpoint — gh api repos/Systemorph/MeshWeaver/rules/branches/main — never the classic protection endpoint, which answers 404 for this repository.

Always build latest

One promotion gate

promote (phases A and B) makes a set usable by CI. It no longer ARMS it. The arming writes — phase C, memex-portal-ai:<version> (what SelfUpdateHostedService lists, pattern 3.0.0-ci*); phase D, the <major>-latest / <major.minor>-latest / 3.0.0-latest pointers; and the signed release event (notify-platform-update) — moved to the arm job, which runs in EVERY main-cd run and arms the newest promoted set that is

  1. newer than every set already armed (never backwards — re-checked at the pointer write), and
  2. carries a success verdict from MeshWeaver.Plugins for exactly its pair — key, core commit, base (the core commit of the newest ARMED set — below) and Plugins commit all checked (.github/scripts/arm-promoted-set.py, --self-test).

A missing verdict WAITS, a red one is REFUSED, a newer green one supersedes both. The verdict is produced asynchronously by MeshWeaver.Plugins' promotion-candidate.yml, which polls for the newest promoted-but-unarmed pair without a verdict and runs core-candidate.yml against it; when that run finishes it dispatches main-cd so the arming does not wait for the hourly reconcile. Core sends nothing to Plugins for this (CoreDispatchesToNoRepository stands); it only READS the verdict ref, which is ledgered in PlatformNeverDependsOnPluginsGuard.ApiReadLedger.

An arming is COMPLETE only when the whole sequence landed — and it is serialised

The cursor arm reads — the newest <version> tag on memex-portal-ai — moves at phase C, before phase D (the line pointers) and before the release event. A failure after phase C would otherwise leave the set counted as armed and never retried. So notify-platform-update writes a non-selectable marker, memex-portal-ai:arm-complete-<version>, only after the build fact was delivered; arm-promoted-set.py armed-state reports whether the newest armed set carries it; and while it does not and nothing newer is green, select --resume <version> re-arms exactly that set (phase C is an idempotent re-tag of the same digest; phase D and the event run again, and the control instance consumes a repeated build fact idempotently). A newer green set still wins — arming it moves the pointers and announces the line past the incomplete one. A set armed before the marker existed has no promotion record to resume from and is reported, never failed.

Two runs can be in arm at once (the push lane and the reconcile lane are separate workflow concurrency groups), so the job carries its own group, main-cd-arm, never cancelled: the read of the cursor, the selection and the phase-C write happen one run at a time. The release event is a separate job and re-checks at the send that no newer set was armed meanwhile, so an older set is never announced after a newer one.

The bundle's base is the newest ARMED set, never the first parent

A promoted set usually carries several core merges — the batch window coalesces them, and pending takes only the newest unarmed pair and supersedes the older ones instead of queueing them — and ALL of them reach the fleet the moment the set is armed. The record's base is what MeshWeaver.Plugins diffs from to SELECT its suites and re-runs its control arm AT, and the verdict is pinned to it. A first-parent base would therefore measure one merge of the bundle and arm every other merge unmeasured — exactly how #5635/#5647/#5655 would reach the fleet if they landed in one set.

So promote's record step takes base from the newest armed set: arm-promoted-set.py armed-base reads memex-portal-ai's manifests (the armed <version> tag shares its manifest with the seven-character sha tag — measured on 3.0.0-ci.9538 / b9fe5ed), the commit is resolved and checked to be an ANCESTOR of the candidate, and base..candidate is then exactly the merges the fleet has not seen. The record says which rule produced base in base_kind:

base_kind when what happens
armed the normal case measured and armed as usual
first-parent nothing was ever armed, or this commit IS the armed set measured and armed; nothing wider exists
unresolved the armed set could not be read, resolved, or is not an ancestor promoted for CI as always, but select REFUSES to arm it and pending does not measure it (a warning names why); the next promoted set retries

An unresolved base never holds CI and never arms merges nobody measured. A record written before this rule carries no base_kind and is judged as before, so no set promoted during the transition is stranded.

What the gate does not hold: promote, verify-images, the platform bake, the Plugins seal, the satellites' compatibility legs and every CI resolver. delivery-verdict judges platform delivery without arm or notify-platform-update (PlatformDeliveryNeverWaitsOnPluginsGuard. TheArmingIsNeverAPlatformDeliveryLeg). What waits for Plugins is only the fleet's roll.

The override. workflow_dispatch of main-cd with arm_override: <set> and arm_reason arms exactly that promoted set without a verdict — for an incident, recorded in the run summary. A set that has no promotion record is refused, never substituted. A single instance is still forced with a Roll Hosting/InstanceAction, not here.

Instances roll only from armed tags — verified

Containment is answerable

Every armed set carries its pair — the tag <core7>-p<plugins7>, the promotion record's full core_sha / plugins_sha, and the release event's coreSha / pluginsSha. "Does image 3.0.0-ci.N contain core commit C / Plugins commit P?" is git merge-base --is-ancestor of C against the record's core commit (P against its Plugins commit) — Systemorph/Memex scripts/image-contains.py, which also resolves the tag an instance runs.

Content-neutral pushes do not restart a Plugins pull-request run

A push that only merges main into a pull request, or regenerates manifest.lock files, changes nothing the pull request authored. MeshWeaver.Plugins' pr-supersede.yml cancels an in-flight run only when the pull request's AUTHORED diff (everything but generated locks, against the respective merge-bases) changed; otherwise the run finishes and the new head ADOPTS its verdict (scripts/ci-change-set.py, adopted-from). This generalises the resolver bot's lock-only-merge rule to any author.

What is still owed

Kept in Systemorph/Memex docs/build-bottlenecks-2026-09-27.md → "Recommendations and status", and filed to bug triage on the control instance.