Skip to content

ci: gen-manifests.py becomes the platform's canonical checker, configured per repo (Plugins#1426) - #3560

Merged
meshweaver-cloud[bot] merged 1 commit into
mainfrom
ci/1426-centralize-gen-manifests
Sep 7, 2026
Merged

meshweaver-cloud[bot] merged 1 commit into
mainfrom
ci/1426-centralize-gen-manifests

Conversation

@rbuergi

@rbuergi rbuergi commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Fleet half of Systemorph/MeshWeaver.Plugins#1426. The repo half is #1427 (merged); this is the decision that issue left open — port the hunk into five copies, or centralize the script — and it is centralize.

The measurement that decided it

repo scripts/gen-manifests.py on main trunk witness (_trunk_commit) --resolve self-test
MeshWeaver.Plugins 1157 yes (#1427) yes yes
MeshWeaver.Crm 646 yes no no
MeshWeaver.Education 646 yes no no
MeshWeaver.Reinsurance 644 yes no no
MeshWeaver.SocialMedia 643 yes no no
MeshWeaver.Manufacturing 305 no no no

One script, five vintages. Education↔Reinsurance differ by 8 lines, Reinsurance↔SocialMedia by 3 — and every one of those lines is the SKIP set. The rest is lag: each fix landed in whichever copy hit the bug and the other five kept it (#434 two witnesses, #942/#1023 --resolve, #1426 the trunk baseline). That is behind, not different — the exact condition AGENTS.md's centralization rule names.

The mechanism is not new. This very lane already fetches three of the platform's scripts at platform-ref — check-workflow-timeouts.py, check-pr-secret-preflight.py, and (in the compile-check lane) compile-check.py, whose header records the same history: "three satellite copies had already drifted apart when this centralization landed". compile-check.py already takes its tree through MW_REPO_ROOT and its policy through an allow-file; MeshWeaver.Reinsurance already ships scripts/platform-script.py to resolve the platform's copy locally. This change walks gen-manifests.py down the same path.

What stays in the caller

One allow-file, scripts/gen-manifests.config.json:

key
skip required. Per-repo by construction — a scratch directory in one repo is a shipping package in another — and equal to validate-repos.py's SKIP, which check-skip-sets.py asserts. No default: a guessed list either demands a manifest.lock for scripts/ or silently stops versioning a real module.
hashModuleSources default false. The #878 fix (hash a mixed package's src/ project and its riding siblings into moduleVersion) needs the caller's project-closure.py to expose graph_of/module_owned/riding_siblings. Asking for it without one is an error, never a quiet fall-back to the smaller hash — and default false because turning it on moves every mixed package's version.

Both are declared, never inferred from whether a file happens to exist: a capability that degrades silently on a missing input is the skip-trapdoor shape AGENTS.md forbids, and here it would change what a published version means without saying so.

Why the divergence did not defeat centralization — it shaped it

Measured, and it is the reason hashModuleSources exists rather than being assumed: Plugins' copy cannot run against MeshWeaver.SocialMedia (AttributeError: module 'project_closure' has no attribute 'module_owned', then riding_siblings — SocialMedia's project-closure.py predates Plugins#1118), and with those stubbed it moves the SocialMedia module's content hash. SocialMedia's own project-closure.py warns in bold against hashing src/ there, answering #878 with a separate --closure gate instead. That is a genuine per-repo design choice, so it became a declared switch — not a lift-and-shift that would have republished a live module.

No gate is skipped

The lane fetches and self-tests the canonical on every call, with no fallback — a ref it cannot be fetched at fails red naming it. The new centralized-gen-manifests input only chooses which copy is believed; a repo that has not adopted yet runs its vendored copy in a step whose name says so. Both branches run a gate; neither setting makes a run silent.

Verified

python3 .github/scripts/gen-manifests.py --self-test    # exit 0

The regression test, before and after. With the #1426 ancestor-of-tip branch removed from _trunk_commit (the pre-#1427 behaviour), the self-test fails:

✗ gen-manifests self-test:
  - a checkout that is an older trunk commit must derive against ITSELF (b04f9a05),
    not the tip (1491fd2f); got '1491fd2f…' (#1426)

and with it restored, exit 0. Its internal control still asserts the fixture does reproduce the red when derived against the tip, so it cannot pass vacuously. --self-test also grew five cases for the config contract: a missing config, a typo'd key (hashModulesSources), a missing project-closure.py, one predating #1118, and the flip actually moving the hash — plus the proof that hashModuleSources: false hashes exactly what a vendored copy hashed.

Equivalence, all six trees as committed — the swap must republish nothing:

repo --check --check-versions
Education identical identical (9 modules)
Reinsurance identical identical (17)
SocialMedia identical identical (5)
Crm identical identical (1)
Manufacturing identical gains the trunk witness — verified against their tags → verified against the published tags and the trunk
Plugins identical identical (61)

The falsification control is the SocialMedia failure above: an unconfigured canonical does move a hash, so a green sweep means something.

check-workflow-timeouts.py --root .: 71 jobs, 0 violations.

What this does not do yet

Nothing changes for any satellite until it opts in — centralized-gen-manifests defaults to false, so an automatic platform-ref bump onto this lane is inert. Adoption is one commit per repo (add the config, drop the vendored copy, point validate-repos.py and check-skip-sets.py at the platform copy, flip the input), landing after this. Those PRs carry the Closes for Plugins#1426.

No What's New entry: CI-internal, nothing a portal user can notice — as Plugins#1427.

Pairs-with: none — no public surface in src/ is touched; the diff is CI scripts and docs.

🤖 Generated with Claude Code

https://claude.ai/code/session_018LzrcSMRzD2R5UcGDk5TKU

Copilot AI lite review requested due to automatic review settings September 7, 2026 08:27

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 new canonical gen-manifests.py prints remediation commands that point to a vendored script path that centralized repos intentionally won’t have, so CI failures would direct users to a non-existent fix command.

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

Pull request overview

This PR centralizes gen-manifests.py as a platform-owned CI checker (mirroring the existing centralized compile-check.py pattern), and updates core documentation and the reusable node-repo-validate workflow to support per-repo configuration via scripts/gen-manifests.config.json.

Changes:

  • Add canonical .github/scripts/gen-manifests.py (with --self-test, trunk-baseline logic, and per-repo config contract).
  • Update .github/workflows/node-repo-validate.yml to fetch + self-test the canonical script and optionally take its verdict via centralized-gen-manifests.
  • Update architecture docs to reflect the centralization approach and the new per-repo config file.
File summaries
File Description
src/MeshWeaver.Documentation/Data/Architecture/ModuleVersioning.md Documents the per-repo gen-manifests.config.json contract and the “one checker, every repo” centralization model.
src/MeshWeaver.Documentation/Data/Architecture/ModuleBuildArchitecture.md Updates the “scripts are centralized” adoption contract to include gen-manifests.py.
src/MeshWeaver.Documentation/Data/Architecture/CiContentBake.md Updates the validate-lane description to reflect platform-fetched gen-manifests.py configured by the caller.
.github/workflows/node-repo-validate.yml Fetches and self-tests the canonical manifest checker at platform-ref, and gates use of it via centralized-gen-manifests.
.github/scripts/gen-manifests.py Introduces the platform’s canonical manifest generator/checker with config validation, trunk witness logic, and self-tests.
Review details

Suppressed comments (1)

.github/scripts/gen-manifests.py:796

  • This remediation message also points to python3 scripts/gen-manifests.py, which won’t exist once a repo centralizes; update it to reference scripts/platform-script.py gen-manifests.py so CI failures give an actually runnable command.
        errors.append("fix: merge the trunk if it has moved, then python3 scripts/gen-manifests.py "
                      "— the tag set AND the trunk baseline were verified against the remote first, "
                      "so the expected numbers above are the ones CI computes")
  • Files reviewed: 5/5 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.

errors.append(f"{plugin.name}: manifest.lock has no valid `version` "
f"(got {existing.get('version')!r}, expected MAJOR.MINOR.PATCH)")
if errors:
errors.append("fix: python3 scripts/gen-manifests.py")
@meshweaver-cloud
meshweaver-cloud Bot enabled auto-merge September 7, 2026 08:35
…gins#1426)

The version checker was vendored in six repos and had drifted to five vintages —
Plugins 1157 lines, Crm 646, Education 646, Reinsurance 644, SocialMedia 643,
Manufacturing 305 — so every fix landed in whichever copy hit the bug and the
other five kept it: #434 (a release has two witnesses), #942/#1023 (--resolve),
and Plugins#1426, where the trunk baseline is resolved against the remote's LIVE
tip. On a push run of main for a commit that is no longer the tip — every
intermediate commit of a merge-queue group — the tree under test is an ancestor
of the baseline it is compared against, so the checker demands a version the tree
never needed and reds. Manufacturing's copy never grew the trunk witness at all,
so it also has no --resolve and no self-test.

.github/scripts/gen-manifests.py is now the canonical copy, fetched by
node-repo-validate.yml at the caller's platform-ref and run against the caller's
tree — the same centralization as compile-check.py, with the same MW_REPO_ROOT
contract and the same no-fallback fetch.

What stays in the caller is one allow-file, scripts/gen-manifests.config.json:

  skip               required. Per-repo by construction, and equal to
                     validate-repos.py's SKIP. No default: a guessed list either
                     demands a manifest.lock for scripts/ or silently stops
                     versioning a real module.
  hashModuleSources  default false. The #878 fix (hash a mixed package's src/
                     project and its riding siblings into moduleVersion) needs the
                     caller's project-closure.py to expose graph_of/module_owned/
                     riding_siblings; asking for it without one is an error, never
                     a quiet fall-back to the smaller hash. Default false because
                     turning it on MOVES every mixed package's version.

Both are declared, never inferred from whether a file exists — a capability that
degrades silently on a missing input is the skip-trapdoor shape AGENTS.md forbids,
and here it would change what a published version means without saying so. The
dot-directory rule only Education's copy had is now everyone's.

The lane fetches and self-tests the canonical on EVERY call; the new
centralized-gen-manifests input only chooses which copy is BELIEVED, so neither
setting skips a gate and a repo that has not adopted runs its vendored copy in a
step that says so. Retiring a caller's vendored copy is a separate step — it stays
its local command and, in most callers, validate-repos.py's library for
check_manifests, until a platform-script.py-style resolver replaces it.

Verified. `--self-test` grew the config contract to five cases (missing config, a
typo'd key, a missing project-closure.py, one predating #1118, and the flip
actually moving the hash) beside the #1426 case and its controls; with the #1426
branch removed from _trunk_commit it fails with "must derive against ITSELF", and
passes with it. Equivalence measured on all six trees as committed: the canonical,
with each repo's declared config, reproduces every lock and every derived version
the vendored copy wrote — --check and --check-versions identical in all six,
including SocialMedia (whose project-closure.py predates #1118 and whose own header
warns against hashing src/ here) and Plugins (hashModuleSources true). Manufacturing
gains the trunk witness: "verified against their tags" becomes "verified against the
published tags and the trunk". The falsification control is that Plugins' copy run
UNCONFIGURED against SocialMedia moves that module's content hash, so the sweep is
not vacuous. check-workflow-timeouts.py: 0 violations.

No What's New entry: CI-internal, nothing a portal user can notice — as Plugins#1427.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018LzrcSMRzD2R5UcGDk5TKU
@rbuergi
rbuergi force-pushed the ci/1426-centralize-gen-manifests branch from f20b2a5 to 3e3a8b5 Compare September 7, 2026 08:41
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

    5 files  ±0      5 suites  ±0   2m 16s ⏱️ +16s
1 483 tests ±0  1 483 ✅ ±0  0 💤 ±0  0 ❌ ±0 
1 484 runs  ±0  1 484 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 3e3a8b5. ± Comparison against base commit 3ab9dbd.

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

492 tests  ±0   492 ✅ ±0   41s ⏱️ -1s
  1 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit 3e3a8b5. ± Comparison against base commit 3ab9dbd.

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

1 034 tests  ±0   1 034 ✅ ±0   1m 16s ⏱️ -1s
    2 suites ±0       0 💤 ±0 
    2 files   ±0       0 ❌ ±0 

Results for commit 3e3a8b5. ± Comparison against base commit 3ab9dbd.

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

251 tests  ±0   251 ✅ ±0   2m 31s ⏱️ +22s
  1 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit 3e3a8b5. ± Comparison against base commit 3ab9dbd.

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

1 608 tests  ±0   1 414 ✅ ±0   2m 12s ⏱️ -1s
    4 suites ±0     194 💤 ±0 
    4 files   ±0       0 ❌ ±0 

Results for commit 3e3a8b5. ± Comparison against base commit 3ab9dbd.

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

1 113 tests  ±0   1 113 ✅ ±0   3m 32s ⏱️ -2s
    3 suites ±0       0 💤 ±0 
    3 files   ±0       0 ❌ ±0 

Results for commit 3e3a8b5. ± Comparison against base commit 3ab9dbd.

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Test Results

   16 files  ±0     16 suites  ±0   12m 31s ⏱️ +36s
5 981 tests ±0  5 787 ✅ ±0  194 💤 ±0  0 ❌ ±0 
5 982 runs  ±0  5 788 ✅ ±0  194 💤 ±0  0 ❌ ±0 

Results for commit 3e3a8b5. ± Comparison against base commit 3ab9dbd.

@meshweaver-cloud
meshweaver-cloud Bot added this pull request to the merge queue Sep 7, 2026
Merged via the queue into main with commit 8893411 Sep 7, 2026
29 checks passed
@rbuergi
rbuergi deleted the ci/1426-centralize-gen-manifests branch October 10, 2026 13:09
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