Skip to content

governance(build): add groups zip family#572

Merged
ChBrain merged 1 commit into
mainfrom
governance/groups-zip-family
May 27, 2026
Merged

governance(build): add groups zip family#572
ChBrain merged 1 commit into
mainfrom
governance/groups-zip-family

Conversation

@ChBrain

@ChBrain ChBrain commented May 27, 2026

Copy link
Copy Markdown
Owner

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.py produced two zip families (cultures-* and engine-*). 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.md to bare dach_position.md and the cultures - Zip build test failed because no zip carried that file.

New 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 three layers:

cultures-<region>-<country>.zip    +
engine-raw.zip                     +
groups-<id>.zip   (for each grouping the culture references)

All extracted into the same folder; every link the culture's files emit resolves.

Implementation

Function Purpose
discover_groupings() Walks groups/<id>/, returns sorted dirs containing at least one .md. Empty dirs are skipped; missing groups/ returns [] (no error — supports branches that predate the layer).
_grouping_items() Collects each grouping's .md files, ships them via _ship() (metadata strip + link flatten) into cultures/<basename>.
build_groups_zips() Emits one groups-<id>.zip per discovered grouping. Wired into build_all() after build_engine_zips().

Grouping basenames like dach_position.md don't collide with country files (culture_<adj>_*.md) or engine basenames, so the flat cultures/ namespace stays collision-free.

Test updates

Test Change
test_culture_zips_resolve_mixed_with_engine Extracts every groups-*.zip alongside 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.md when extracting cultures-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) Each groups-*.zip extracts cleanly on its own with no broken intra-zip links. Skips when groups/ is absent (current state on main).
test_discover_groupings_skips_empty (new) Fixture-based; verifies empty grouping dirs don't produce zips.
test_build_groups_zips_produces_one_per_grouping (new) Fixture-based; one zip per non-empty grouping, files shipped into cultures/.
test_build_groups_zips_absent_directory_returns_empty (new) Fixture-based; no 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

Run Result
Clean main (no groups/) 17 pass, 1 skip ✅
Simulated culture/release + #564 overlay (groups/dach/ + DACH-linked position files) 18 pass ✅
python tests/branch_scope.py advise --files scripts/build_zips.py tests/test_build_zips.py governance/<name> (base main) ✅

Sequencing

  1. This PR → main
  2. Open sync/release-from-main-zip-familyculture/release
  3. culture(europe): bake DACH group membership into member positions #564 picks up the new build_zips.py and the Zip build job passes (it already has ## Title chapters and v2 schema in place via the DACH migrations)

Ref: #564, #563, #566


Generated by Claude Code

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.
@ChBrain
ChBrain merged commit 6a0258a into main May 27, 2026
53 checks passed
@ChBrain
ChBrain deleted the governance/groups-zip-family branch May 27, 2026 08:02
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