Skip to content

State the framework identity as the platform commit instead of deriving it per job (#3308) - #3339

Merged
meshweaver-cloud[bot] merged 2 commits into
mainfrom
fix/3308-state-the-framework-identity
Sep 5, 2026
Merged

meshweaver-cloud[bot] merged 2 commits into
mainfrom
fix/3308-state-the-framework-identity

Conversation

@rbuergi

@rbuergi rbuergi commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

This is red on core main right now

Run 33950389008 (2026-09-05 07:05Z) — four entries, one platform commit, every leg
plan: BUILD (no reuse) — stated three identities, and #3310's agreement gate refused the lane.
Core CD has failed on it repeatedly today; the one green run in between skipped the module-pack
job entirely, so it attested nothing.

module compiler builds in the job stated identity
MeshWeaver.Maps 1 0051e7210e4144a6a18620a36e629d84
MeshWeaver.Payments.Stripe 1 0051e7210e4144a6a18620a36e629d84
MeshWeaver.Markdown.Collaboration 2 6553ea371eab49e4afdd0af20ea7ac59
MeshWeaver.AI 2 395909c22d3042d4b23fb055337db63b

Why the two previous cures could not have worked

The split is exactly the reference graph, and the lane's own comment already says so:
MeshWeaver.AI and MeshWeaver.Markdown.Collaboration reach MeshWeaver.Compiler transitively.
So the module build compiles it first — into the same output path, under the module's own
global properties — and the anchor build that follows keeps those bytes. Maps and Payments.Stripe
never build it, so their anchor build compiles it clean and they agree with each other.

Both fixed the anchor build. The pollution arrives from the module build upstream, which is
entitled to its own version. No property removable from the anchor build can reach it.

And determinism alone would not have been enough

A portal image is built with -p:CIRun=true, so AddCommitHashMetadata stamps
AssemblyMetadata("MeshWeaverFrameworkIdentity", "g<sha>"), and FrameworkIdentity.ReadIdentity
prefers that stamp over the MVID. A consumer reads g<sha>; a source-built bundle stated
32-hex. Never equal, by construction — so #3154's (version, identity) comparison could only
ever answer "identity could not be checked", which is the exact state #3211 exists to prevent.
Even a perfectly stable MVID would have been unmatchable.

The change

The from-source arm stops deriving and states it. The platform IS this checkout, its commit IS
what a consumer reads, and --framework-mvid is the packer's own flag for that — its own error
text names g<sha> as a legal token, and the only validation is "no whitespace".

sha="$(git -C "$GITHUB_WORKSPACE/meshweaver" rev-parse HEAD 2>/dev/null || true)"
# … 40-hex or RED, never a fallback …
identity=(--framework-mvid "g$sha")

One identity per platform commit, identical for every entry, and no compiler build per matrix
job at all
— which also removes a full dotnet build from every leg.

The refs arm is unchanged: there the DLL genuinely is the platform, is already stamped, and
already yields g<sha>. This makes the two arms agree rather than inventing a third shape.

RECIPE_VERSION 2 → 3, because bundles packed under recipe 2 state an identity no consumer can
match and must be repacked, not reused.

The guard moves with its subject, and gets stronger

It used to pin where the anchor build lived — a config-level assertion that was green through all
three incarnations of this bug. It now forbids MeshWeaver.Compiler.csproj in the pack step
at all: the per-job build is the mechanism every previous cure left in place, and it packs
GREEN while handing #3154 a value no consumer can match.

Mutation-proved: reintroducing that build turns the guard red (Assert.DoesNotContain() Failure: Sub-string found), and it passes with it gone.

Verification

Closes #3308

…ng it per job (#3308)

Core CD is RED on this today: run 33950389008 (2026-09-05 07:05Z, four entries,
ONE platform commit, every leg `plan: BUILD`) stated THREE identities, and
#3310's agreement gate refused the lane —

    MeshWeaver.Maps                    0051e7210e4144a6a18620a36e629d84  (1 compiler build)
    MeshWeaver.Payments.Stripe         0051e7210e4144a6a18620a36e629d84  (1 compiler build)
    MeshWeaver.Markdown.Collaboration  6553ea371eab49e4afdd0af20ea7ac59  (2 compiler builds)
    MeshWeaver.AI                      395909c22d3042d4b23fb055337db63b  (2 compiler builds)

The split is exactly the reference graph. MeshWeaver.AI and
MeshWeaver.Markdown.Collaboration reach MeshWeaver.Compiler transitively, so the
MODULE build compiles it FIRST — into the same output path, under the module's
own global properties — and the anchor build that follows keeps those bytes. Maps
and Payments.Stripe never build it, so their anchor build compiles it clean and
they agree with each other.

That is why the two previous cures could not work. #3293 moved the anchor build
into the platform tree; #3306 removed the `-p:Version` polluting it. Both fixed
the ANCHOR build, and the pollution arrives from the MODULE build upstream, which
is entitled to its own version.

And a derived MVID could not be matched even if it were stable. A portal image is
built with -p:CIRun=true, so AddCommitHashMetadata stamps
AssemblyMetadata("MeshWeaverFrameworkIdentity", "g<sha>") and
FrameworkIdentity.ReadIdentity PREFERS that stamp over the MVID. A consumer reads
`g<sha>`; a source-built bundle stated 32-hex. Never equal, by construction — so
#3154's (version, identity) comparison could only ever answer "identity could not
be checked", which is the exact state #3211 exists to prevent.

So the arm stops deriving and STATES it: the platform IS this checkout, its commit
IS what a consumer reads, and `--framework-mvid` is the packer's own flag for that
(its own error text names `g<sha>` as a legal token). One identity per platform
commit, identical for every entry, and no compiler build per matrix job at all.
The refs arm is unchanged: there the DLL genuinely IS the platform, already
stamped, and already yields `g<sha>` — so this makes the two arms agree rather
than inventing a shape.

An unreadable platform commit is RED, never a fallback.

RECIPE_VERSION 2 → 3: bundles packed under recipe 2 state an identity no consumer
can match, so they must be repacked rather than reused.

The guard moves with its subject and gets STRONGER: instead of pinning the
location of an anchor build, it now forbids `MeshWeaver.Compiler.csproj` in the
pack step at all — the mechanism all three previous cures left in place. Mutation-
proved: reintroducing that build turns the guard red.

Verified: Documentation.Test 0 Error(s) Release -warnaserror; the identity guards
5/5; node-repo-pack-verify.py --self-test passes.

Closes #3308

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 5, 2026 07:56
@meshweaver-cloud
meshweaver-cloud Bot enabled auto-merge September 5, 2026 07:56

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’s SHA validation currently accepts short hex prefixes (8+ chars) even though the identity format is documented as g<full-commit-sha> (40 hex), which can reintroduce mismatched identities.

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

Pull request overview

This PR fixes the reusable module-pack workflow’s “from-source” arm so it states the framework identity as the platform commit (g<sha>) instead of deriving it from a per-matrix-job build artifact, eliminating identity skew across matrix entries and avoiding an extra compiler build per job.

Changes:

  • Update .github/workflows/node-repo-module-pack.yml to compute identity=(--framework-mvid "g$sha") from the checkout commit when no platform image (REFS) is pinned, and pass that identity to the pack tool.
  • Strengthen the documentation test guard to ratchet against reintroducing per-job MeshWeaver.Compiler.csproj builds in the pack step.
  • Bump the module build key RECIPE_VERSION to force repacking rather than reusing bundles produced under the previous (unmatchable) identity recipe.
File summaries
File Description
test/MeshWeaver.Documentation.Test/ModuleIdentityPublishGuard.cs Updates the guard expectations to require “state identity” behavior and forbid compiler builds in the pack step.
.github/workflows/node-repo-module-pack.yml Changes the from-source arm to state --framework-mvid g<sha> and removes --graph-dll derived identity in that arm.
.github/scripts/module-build-key.py Bumps RECIPE_VERSION to invalidate/repack older bundles under the new identity recipe.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 1
  • Review effort level: Lite

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

Comment on lines +1799 to +1807
sha="$(git -C "$GITHUB_WORKSPACE/meshweaver" rev-parse HEAD 2>/dev/null || true)"
case "$sha" in
[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]*) : ;;
*)
echo "::error::cannot read the platform commit at \$GITHUB_WORKSPACE/meshweaver (git rev-parse HEAD gave '${sha:-<nothing>}'). No platform image is pinned, so the checkout IS the platform and its commit IS the identity every bundle of this run must state (#3308). Nothing here may guess it, so the pack stops."
exit 1
;;
esac
identity=(--framework-mvid "g$sha")
@github-actions

github-actions Bot commented Sep 5, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

475 tests   475 ✅  39s ⏱️
  1 suites    0 💤
  1 files      0 ❌

Results for commit 8286112.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 5, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

369 tests   367 ✅  42s ⏱️
  3 suites    2 💤
  3 files      0 ❌

Results for commit 8286112.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 5, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

251 tests   251 ✅  2m 0s ⏱️
  1 suites    0 💤
  1 files      0 ❌

Results for commit 8286112.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 5, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

    4 files      4 suites   1m 42s ⏱️
1 501 tests 1 501 ✅ 0 💤 0 ❌
1 502 runs  1 502 ✅ 0 💤 0 ❌

Results for commit 8286112.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 5, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

2 315 tests   2 315 ✅  2m 36s ⏱️
    3 suites      0 💤
    3 files        0 ❌

Results for commit 8286112.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 5, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

588 tests   396 ✅  2m 56s ⏱️
  3 suites  192 💤
  3 files      0 ❌

Results for commit 8286112.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 5, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

   15 files     15 suites   10m 38s ⏱️
5 499 tests 5 305 ✅ 194 💤 0 ❌
5 500 runs  5 306 ✅ 194 💤 0 ❌

Results for commit 8286112.

♻️ This comment has been updated with latest results.

…stderr or the exit status

check-workflow-shell.py caught it and is right: `sha="$(git … 2>/dev/null || true)"`
swallows BOTH git's message and its exit status, so a checkout without .git would
have reached the validation as a blank string with no trace of why. The refusal
was still fail-closed, but the log could not have said what went wrong.

Now `if ! sha="$(git … rev-parse HEAD)"` — `set -e` stays out of the way until
the explanation is printed, and git's own stderr lands in the log. The shape check
is also a real 40-hex assertion rather than an 8-character prefix glob.

check-workflow-shell.py run locally: 0 live findings (the 1 remaining is the
pre-existing allow-listed entry). Documentation.Test 0 Error(s) 0 Warning(s)
Release -warnaserror; ModuleIdentityPublishGuard 5/5.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@rbuergi

rbuergi commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

The two reds, attributed

1. CI's own shell — mine, and the gate was right

check-workflow-shell.py:

[swallow] a CAPTURED command substitution swallows both stderr and the exit status (step 'Pack
the module bundle') — a failed call becomes an empty answer with no trace.

sha="$(git … rev-parse HEAD 2>/dev/null || true)" threw away git's message and its exit status.
The refusal downstream was still fail-closed, but a checkout without .git would have arrived at it
as a blank string with nothing in the log saying why — which is the same "cannot tell 'looked and
found nothing' from 'could not look'" shape this PR is about, one level down.

Fixed by letting it fail out loud:

if ! sha="$(git -C "$GITHUB_WORKSPACE/meshweaver" rev-parse HEAD)"; then
  echo "::error::… git rev-parse HEAD failed and its message is above …"
  exit 1
fi
if [ ${#sha} -ne 40 ] || [ -n "${sha//[0-9a-f]/}" ]; then …

set -e stays out of the way until the explanation prints, git's stderr reaches the log, and the
shape check is now a real 40-hex assertion instead of an 8-character prefix glob.

Verified locally: check-workflow-shell.py → 0 live findings (the one remaining is the
pre-existing allow-listed entry). Documentation.Test 0 Error(s) 0 Warning(s),
ModuleIdentityPublishGuard 5/5.

2. Run tests (shard 3) — not this diff

BakeEquivalenceTest.MeshDrivenAndCompilerDrivenBakes_ProduceTheSameArtifacts failed inside a
teardown/disposal race, not on anything this PR touches:

HubDisposedBeforeResponseException: Hub Widget/Thing was disposed before the response arrived
ObjectDisposedException: Hub Widget/Thing is shutting down — cannot register new response subject
HostedHubsCollection: Rejecting hosted hub creation … during disposal - collection is disposing
[ReleasePostCondition] Widget/Thing: … the node names NO release at all

That is #3333 ("a mesh shutting down mid-bake, and a faulted change-feed entry whose release
does not claim it"), already filed. This diff changes a workflow, one Python constant
(RECIPE_VERSION) and a guard test — it reaches no code in MeshWeaver.PluginTester, and the same
suite passed on #3337's run from the same base. Not re-running it to see whether it goes green: that
would only hide the race #3333 exists to fix.

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.

A source-built bundle's frameworkMvid is per-MODULE and unmatchable by any consumer

2 participants