governance(build): add groups zip family#572
Merged
Merged
Conversation
scripts/build_zips.py only produced two zip families (cultures-* and
engine-*). The groupings layer (groups/<id>/) had no shipping path, so
the moment a culture file linked to a grouping (e.g. DACH baking on
the three member position files referencing groups/dach/dach_position.md),
the link flattener rewrote it to bare dach_position.md and the
combined extract test failed because no zip carried that file.
This PR adds the missing family:
groups-<id>.zip one per directory under groups/; carries that
grouping's .md files plus LICENSE, flattened into
the same cultures/ namespace as engine files.
Deployer contract becomes:
cultures-X.zip + engine-raw.zip + groups-Y.zip (for each Y the
culture references) extracted into the same folder; every link the
culture's files emit resolves.
Implementation:
- discover_groupings() walks groups/<id>/ and returns sorted dirs
containing at least one .md (empty dirs are skipped, missing groups/
is treated as "no groupings" without error).
- _grouping_items() collects .md files and ships them via _ship()
(metadata strip + link flatten) into cultures/<basename>. Grouping
basenames like dach_position.md don't collide with country files
(culture_<adj>_*.md) or engine basenames, so the flat namespace
stays collision-free.
- build_groups_zips() emits one groups-<id>.zip per discovered
grouping. Wired into build_all() after build_engine_zips().
Test updates:
- test_culture_zips_resolve_mixed_with_engine now extracts every
groups-*.zip alongside each culture zip + engine-raw.zip, and
filters broken-link reports to only fire on outbound links from the
CULTURE's own files. Inbound back-links from a grouping to a
non-co-extracted member culture (e.g. dach_position.md ->
culture_austrian_position.md when extracting cultures-africa-nigeria)
are not a defect of nigeria's deployment unit -- those resolve when
the multi-member region/world zip is the culture vehicle.
- test_groups_zips_self_consistent (new) verifies each groups-*.zip
extracts cleanly on its own, filtering out the same kind of
cross-layer back-link the previous test handles.
- Three new fake_groups fixture tests exercise discover_groupings()
and build_groups_zips() against synthetic trees so the behavior is
validated deterministically even on a branch that predates the
groupings layer (no groups/ directory at all).
Unblocks PR #564 (DACH baking on culture/europe): once this lands on
main and syncs to culture/release, #564's CI picks up the new
build_zips.py and its Zip build job stops failing.
Once a grouping exists on the branch under test (e.g. on
culture/release after #566 + this sync), the self-consistency test
activates; until then it skips cleanly.
This was referenced May 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the missing zip family for the groupings layer. Unblocks PR #564 once it lands on main and syncs to
culture/release.Why now
scripts/build_zips.pyproduced two zip families (cultures-*andengine-*). The groupings layer (groups/<id>/, introduced via #563 + #566) had no shipping path. The moment a culture file linked to a grouping (e.g. the DACH baking on Germany / Austria / Switzerland position files), the link flattener rewrote../../groups/dach/dach_position.mdto baredach_position.mdand thecultures - Zip buildtest failed because no zip carried that file.New family
Deployer contract becomes three layers:
All extracted into the same folder; every link the culture's files emit resolves.
Implementation
discover_groupings()groups/<id>/, returns sorted dirs containing at least one.md. Empty dirs are skipped; missinggroups/returns[](no error — supports branches that predate the layer)._grouping_items().mdfiles, ships them via_ship()(metadata strip + link flatten) intocultures/<basename>.build_groups_zips()groups-<id>.zipper discovered grouping. Wired intobuild_all()afterbuild_engine_zips().Grouping basenames like
dach_position.mddon't collide with country files (culture_<adj>_*.md) or engine basenames, so the flatcultures/namespace stays collision-free.Test updates
test_culture_zips_resolve_mixed_with_enginegroups-*.zipalongside each culture zip +engine-raw.zip. Filters broken-link reports to only fire on outbound links from the culture's own files. Inbound back-links from a grouping to a non-co-extracted member culture (e.g.dach_position.md → culture_austrian_position.mdwhen extractingcultures-africa-nigeria) are not a defect of Nigeria's deployment unit — they resolve when the multi-member region/world zip is the culture vehicle.test_groups_zips_self_consistent(new)groups-*.zipextracts cleanly on its own with no broken intra-zip links. Skips whengroups/is absent (current state onmain).test_discover_groupings_skips_empty(new)test_build_groups_zips_produces_one_per_grouping(new)cultures/.test_build_groups_zips_absent_directory_returns_empty(new)groups/→ returns[], no error.The fixture tests use a synthetic tree so behavior is validated deterministically even on this branch where no real
groups/exists yet.Verification
groups/)python tests/branch_scope.py advise --files scripts/build_zips.py tests/test_build_zips.pygovernance/<name>(basemain) ✅Sequencing
mainsync/release-from-main-zip-family→culture/releasebuild_zips.pyand the Zip build job passes (it already has## Titlechapters and v2 schema in place via the DACH migrations)Ref: #564, #563, #566
Generated by Claude Code