Skip to content

ci(scripts): a platform canary — tell a node repo on the DAY core breaks it, not at the next pin bump - #3806

Merged
meshweaver-cloud[bot] merged 2 commits into
mainfrom
feat/1519-platform-canary
Sep 9, 2026
Merged

meshweaver-cloud[bot] merged 2 commits into
mainfrom
feat/1519-platform-canary

Conversation

@rbuergi

@rbuergi rbuergi commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Implements the platform-canary proposal in Systemorph/MeshWeaver.Plugins#1519. The caller workflow is a separate change in that repository.

The scheduled canary compares selected dependent suites at their current core pin and at one resolved core-main commit. It reports compatibility regressions without becoming a required PR check or moving the pin.

Evidence and outcomes

  • Each arm uses its own caller checkout and builds Release with CI warnings-as-errors flags, then runs tests against that build.
  • Test regressions require a recorded Passed result at the pin and Failed at main, read from fresh TRX files. A missing failure line is never interpreted as a pass.
  • A compiler failure at main is reported as a build regression only when the same suite built successfully at the pin. Other process/build failures are incomplete evidence.
  • Missing, partial, empty, skipped-only, ambiguous, or inconsistent results prevent a clean verdict. An incomplete comparison cannot close an existing tracking issue. A baseline build failure does not cancel against another failed build and become green.
  • A missing or skipped candidate test also cannot establish that an earlier failure healed.
  • Per-suite build/test logs, TRX files, and arm evidence are retained as artifacts. The 45-minute job cap remains; each build and test invocation is bounded at eight minutes.
  • Tracking-issue discovery uses REST and refuses multiple matching issues.

Verification

python3 .github/scripts/test-platform-canary.py: 15 tests pass, covering the demonstrated baseline-build-failure false regression, real pass-to-fail comparisons, compiler versus infrastructure failure, missing/skipped tests, partial results, host exits, duplicate names, and theory names. The parser also successfully read an actual five-test xUnit TRX from the local radio-binding reproduction.

Workflow shell, YAML-key, timeout, and secret-preflight checks pass. The actual scheduled comparison still needs its first run after the dependent caller is wired; fixture tests do not establish that the dependent suites pass against core main.

No What's New entry: internal CI reporting only.

Pairs-with: none — CI only; no public framework surface changes.

…aks it, not at the next pin bump

MeshWeaver.Plugins#1519. Twice on 2026-09-08 a core behaviour change behind an UNCHANGED SIGNATURE
reddened that repo, and both times it was found by the pin bump rather than before it:

  Plugins#1506  core #3681 restricted the legacy access heal to pre-installed partitions — found by
                reading core's open PRs. Luck, not a gate.
  Plugins#1518  core #3648 made the platform floor ADVISORY; 14 tests asserted the hold/refuse/skip
                it replaced — found by the bump itself, WITH THE TRUNK ALREADY RED and every open
                pull request blocked on a gate none of their diffs could reach.

The structural reason is not incidental: no surface detector sees this class by construction — the
cross-repo pair gate looks for REMOVED types and members, and nothing was removed — and for a
dependent that PINS core, the release event resolves the same pin a pull request does. So the pin
bump IS the integration test, and wherever the App lacks `workflows` (Plugins#1464/#1547) that bump
is a hand move, which in practice happens when the staleness ratchet has already forced it.

This lane runs that same integration test on a schedule, moving nothing.

🚨 IT NEVER GATES. Core main moves under it, so a red canary must never block a pull request that is
fine at the pin — a caller that made this required would be blocked by someone else's core merge. It
reports; a human decides when to adopt.

🚨 THE CONTROL ARM IS THE LOAD-BEARING HALF. The suites run TWICE — once at the caller's current pin,
once at core main — and only tests that PASS at the pin and FAIL at main are reported. Without it
every pre-existing red reads as pin drift, the report is noise, and the lane is ignored inside a
week: exactly the failure AGENTS.md names for a gate that is red when nothing is wrong, and the one
Plugins#1510 had just finished fixing in compile-check.py.

Details that are deliberate rather than incidental:

  · core main is resolved ONCE and both checkouts read that output — two checkouts of a branch name
    in one run legitimately take different commits, which is what ci.yml's platform-ref job exists
    for.
  · a suite that cannot BUILD at one arm is a RESULT ("<suite>: BUILD FAILED"), not a reason to
    abandon the comparison; a build broken at both arms cancels in the delta, which is the control
    working.
  · the tracking issue is edited and re-commented while the delta persists and CLOSED when core main
    stops breaking anything — the same report shape as the platform-ref bump lane, because a
    scheduled red surfaces to nobody.
  · `suites` is required with no default: running everything would cost more and report the same,
    and guessing which suites matter for a caller is exactly the kind of default that silently
    measures nothing.

Verified offline before shipping: the workflow parses; the failed-test extraction reads real xUnit
output including theory cases with parenthesised arguments; and the delta over fixtures reports only
the newly-broken test, excluding one already red at the pin and one whose build broke at both arms.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 9, 2026 09:49
@rbuergi
rbuergi enabled auto-merge September 9, 2026 09:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The workflow currently contains a bash syntax error in the pin/main resolution step and uses continue-on-error in a way that can silently misreport a failed run as “clean.”

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds a reusable GitHub Actions workflow that runs a “platform canary” for node repos by executing selected test suites twice (once against the repo’s pinned core commit, once against core main) and filing/updating a tracking issue with only the delta (tests that pass at the pin but fail at main).

Changes:

  • Introduces .github/workflows/node-repo-platform-canary.yml as a workflow_call reusable workflow with inputs for pin location and targeted test suites.
  • Resolves the upstream repo’s default-branch HEAD SHA once per run, checks out both core SHAs, runs suites against both, and computes/report deltas.
  • Opens/updates/closes a single tracking issue (by marker) to make scheduled failures visible and self-clearing.
File summaries
File Description
.github/workflows/node-repo-platform-canary.yml New reusable workflow implementing the pin-vs-main delta canary, with issue-based reporting.
Review details

Suppressed comments (1)

.github/workflows/node-repo-platform-canary.yml:171

  • Same as above: continue-on-error: true can hide an arm failing to run at all, which would make the pin-vs-main comparison unreliable (and could lead to closing an existing tracking issue on no evidence).
      - name: Run the suites at core MAIN
        continue-on-error: true
        env:
  • Files reviewed: 1/1 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

# legitimately take two different commits; core merges several times an hour.
echo "pin=$pin" >> "$GITHUB_OUTPUT"
echo "main=$main" >> "$GITHUB_OUTPUT"
echo "behind=$(gh api "repos/${UPSTREAM}/compare/${pin}...${main}" --jq .ahead_by)" >> "$GITHUB_OUTPUT"
@github-actions

github-actions Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

496 tests  ±0   496 ✅ ±0   43s ⏱️ +5s
  1 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit d36f769. ± Comparison against base commit ea4df2d.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

389 tests  ±0   389 ✅ ±0   46s ⏱️ -9s
  2 suites ±0     0 💤 ±0 
  2 files   ±0     0 ❌ ±0 

Results for commit d36f769. ± Comparison against base commit ea4df2d.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

254 tests  ±0   254 ✅ ±0   2m 11s ⏱️ -4s
  1 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit d36f769. ± Comparison against base commit ea4df2d.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

1 869 tests  +3   1 869 ✅ +3   2m 41s ⏱️ +2s
    4 suites ±0       0 💤 ±0 
    4 files   ±0       0 ❌ ±0 

Results for commit d36f769. ± Comparison against base commit ea4df2d.

♻️ This comment has been updated with latest results.

@rbuergi

rbuergi commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Additional evidence for the canary comparison at head 5221b8f553531b6b3eeedd75c9435792e5fb9171: I extracted the exact canary-run.sh from this workflow and ran it against a synthetic dotnet command. At the PIN the command returned a compiler error (no tests ran); at MAIN it returned Example.Tests.TestOne [FAIL]. Both arm runners exited 0. Their files were:

pin.txt:  src/Example.Test: BUILD FAILED
main.txt: Example.Tests.TestOne

The workflow's set difference then reports Example.Tests.TestOne as “passes at the pin, fails at core main”, although it never ran at the pin. This is separate from a missing output file: both expected files exist and contain a classified result. Removing continue-on-error or the touch alone will not fix it.

Please make the baseline require positive per-test pass evidence (fresh TRX), and classify build/host failures as an incomplete comparison rather than a test pass. Incomplete arms must not close a prior tracking issue. A fixture covering baseline-build-failure + candidate-test-failure should pin this distinction. This is a local workflow-logic reproduction, not a real failure in a dependent suite.

Also, the earlier review's nested-quote warning on echo "behind=$(gh api "repos/..." ...)" is not by itself a Bash syntax error: command-substitution parsing has its own quoting context. The evidence/comparison problem above is the actionable one.

@github-actions

github-actions Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

1 142 tests  ±0   1 140 ✅ ±0   3m 41s ⏱️ +2s
    3 suites ±0       2 💤 ±0 
    3 files   ±0       0 ❌ ±0 

Results for commit d36f769. ± Comparison against base commit ea4df2d.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

    6 files  ±0      6 suites  ±0   4m 38s ⏱️ -2s
2 509 tests ±0  2 317 ✅ ±0  192 💤 ±0  0 ❌ ±0 
2 510 runs  ±0  2 318 ✅ ±0  192 💤 ±0  0 ❌ ±0 

Results for commit d36f769. ± Comparison against base commit ea4df2d.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

   17 files  ±0     17 suites  ±0   14m 43s ⏱️ -6s
6 659 tests +3  6 465 ✅ +3  194 💤 ±0  0 ❌ ±0 
6 660 runs  +3  6 466 ✅ +3  194 💤 ±0  0 ❌ ±0 

Results for commit d36f769. ± Comparison against base commit ea4df2d.

♻️ This comment has been updated with latest results.

@rbuergi
rbuergi added this pull request to the merge queue Sep 9, 2026
@rbuergi

rbuergi commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor Author

Verified update: d36f769 is now the remote head of this PR. The first push was rejected by the merge queue; I dequeued the PR through dequeuePullRequest, pushed successfully without force, and verified the head with REST. Fifteen focused tests and workflow checks passed locally. Fresh CI/review must complete before this correction merges.

@rbuergi
rbuergi removed this pull request from the merge queue due to a manual request Sep 9, 2026
@meshweaver-cloud

Copy link
Copy Markdown
Contributor

ℹ️ Merge-queue steward: no action — removed from the queue with reason MANUAL — the steward takes no action for this reason.

@meshweaver-cloud
meshweaver-cloud Bot enabled auto-merge September 9, 2026 10:11
@meshweaver-cloud
meshweaver-cloud Bot added this pull request to the merge queue Sep 9, 2026
Merged via the queue into main with commit fe40173 Sep 9, 2026
33 checks passed
@rbuergi

rbuergi commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

🚨 There are now TWO canaries from Plugins#1519 — this lane, and an inline one in MeshWeaver.Plugins

Flagging a duplication I am partly responsible for. Both landed on 2026-09-09 from the same issue:

this PR node-repo-platform-canary.yml, reusable, 332 lines — merged 10:31Z
MeshWeaver.Plugins .github/workflows/platform-canary.yml, inline per-repo, 272 lines

I filed #1519 and built the shared lane; someone independently built the Plugins one. Neither of us
saw the other.

They are not redundant copies — each has something the other lacks

The Plugins one is better structured in two ways I did not do:

  • a matrix over the two arms ("check out the platform at this arm's commit"), so the pin arm
    and the main arm run in parallel rather than sequentially;
  • a comparator self-test — "Prove the comparator can fail" — which is exactly the anti-vacuity
    discipline this repo applies everywhere else, and which my delta step does not have.

This lane has what that one does not: it files and closes a tracking issue, which is the half
that makes a scheduled red visible at all (the lesson from the platform-ref bump lane going red daily
into an empty room for weeks — Plugins#1547).

The convergence, and it is the pattern we just used twice today

Same shape as compile-check.py (core #3761) and gen-manifests.py (Plugins#1426): port the
better parts UP into the shared lane, then make the caller call it.
Doing it the other way — having
Plugins keep an inline copy — recreates exactly the drift the fleet just finished removing, and it
does so in the one lane whose whole purpose is catching cross-repo drift.

Concretely: this lane takes the matrix and the comparator self-test; Plugins' platform-canary.yml
becomes a ~20-line caller with its suites: list; the five satellites then get the same canary for
free, which none of them has today.

I am not doing that unilaterally — the Plugins implementation is somebody's fresh work and they may
have reasons for the inline shape I cannot see (its "build the canary set as one graph" step suggests
a build strategy this lane does not model). Whoever owns it: say which way, and I will do the port.

Leaving both as they are until then is the one option that should not stand — two canaries mean two
answers to "would core main break this repo", and the first time they disagree nobody will know which
to believe.

@rbuergi
rbuergi deleted the feat/1519-platform-canary branch October 10, 2026 13:10
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.

2 participants