feat(topdata): parts autogen from CDN part.yml channel (Phase 2) (#25)
Phase 2 of the parts-row autogen design — Crucible (sow-tools) side only. Resolves parts 2DA rows from the immutable \`part.yml\` published with the asset HAK release, over the anonymous Bunny CDN channel, with the parts consumer **required** (no fail-open). ## Changes - **Resolver (T3):** generalize \`resolveCDNChannelManifest\` — derive manifest basename + provenance label from config; drop hardcoded \`assets/vfxs.yml\` so \`part.yml\` resolves through the same path. VFX behavior unchanged. - **Parts filter (T4):** \`filterCDNChannelEntries\` dispatches on consumer mode; new \`filterPartsCDNChannelEntries\` implements the inventory contract — \`restype: mdl\` under \`part/<supported-cat>/\`, row id from trailing digits, dedup l/r/race/gender variants by (category,rowID), sort by source, **zero accepted rows = hard fail**, reject row id 0 / non-numeric / malformed / no-assets. - **Category (T5):** add \`hand\` + \`parts/hand\` mapping (12 datasets; \`leg→legs\` already present). - **Pipeline (T6):** native build runs one explicit augment → normalize → override sequence under the configured \`parts_rows\` policy; overrides may update an existing/discovered row but **never synthesize** one (orphan override now errors). - **Validation (T7):** reject more than one \`parts_rows\` consumer. ## Tests 9 new tests: parts filter contract, 12-dataset coverage, offline manifest-basename, orphan-override reject + discovered-row update, and a parity guard proving a bare CDN-only parts build needs no wrapper/token/NWN_ROOT/checkout. Gate green: \`go test ./internal/topdata/... ./internal/project/...\` + full \`go build ./... && go test ./...\`. ## Scope / ordering Phase 2 is inert until **Phase 1** (sow-assets-manifest publishes \`part.yml\`) ships and the operational **Gate** (release publish/promote) runs, then **Phase 3** enables the sow-topdata consumer. Do not enable the consumer against a channel whose current release predates \`part.yml\`. Plan: \`sow-topdata/docs/superpowers/plans/2026-06-25-topdata-parts-autogen-channel.md\` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed-on: #25 Co-authored-by: vickydotbat <vickydotbat@tutamail.com> Co-committed-by: vickydotbat <vickydotbat@tutamail.com>
This commit was merged in pull request #25.
This commit is contained in:
@@ -21,6 +21,7 @@ var supportedPartCategories = []string{
|
||||
"chest",
|
||||
"foot",
|
||||
"forearm",
|
||||
"hand",
|
||||
"leg",
|
||||
"neck",
|
||||
"pelvis",
|
||||
@@ -35,6 +36,7 @@ var partDatasetToAssetCategory = map[string]string{
|
||||
"chest": "chest",
|
||||
"foot": "foot",
|
||||
"forearm": "forearm",
|
||||
"hand": "hand",
|
||||
"legs": "leg",
|
||||
"neck": "neck",
|
||||
"pelvis": "pelvis",
|
||||
@@ -390,6 +392,18 @@ func applyPartOverrides(sourceDir string, collected []nativeCollectedDataset) ([
|
||||
return applyPartOverridesWithConfig(sourceDir, collected, project.PartsRowsConfig{})
|
||||
}
|
||||
|
||||
// partsRowsConfigForProject returns the PartsRows policy of the single configured
|
||||
// parts_rows autogen consumer, or the zero value if none. Project validation
|
||||
// guarantees at most one parts_rows consumer, so the first match is canonical.
|
||||
func partsRowsConfigForProject(p *project.Project) project.PartsRowsConfig {
|
||||
for _, c := range p.Config.Autogen.Consumers {
|
||||
if strings.TrimSpace(c.Mode) == "parts_rows" {
|
||||
return c.PartsRows
|
||||
}
|
||||
}
|
||||
return project.PartsRowsConfig{}
|
||||
}
|
||||
|
||||
func applyPartOverridesWithConfig(sourceDir string, collected []nativeCollectedDataset, cfg project.PartsRowsConfig) ([]nativeCollectedDataset, error) {
|
||||
result := make([]nativeCollectedDataset, len(collected))
|
||||
copy(result, collected)
|
||||
@@ -430,9 +444,7 @@ func applyPartOverridesWithConfig(sourceDir string, collected []nativeCollectedD
|
||||
}
|
||||
row, ok := rowByID[rowID]
|
||||
if !ok {
|
||||
row = createDefaultPartRowForDataset(rowID, dataset.Dataset.Name, dataset.Columns, cfg)
|
||||
rows = append(rows, row)
|
||||
rowByID[rowID] = row
|
||||
return nil, fmt.Errorf("parts/%s override %d targets row id %d which is neither a baseline row nor a discovered model row; author the row in data/parts/%s.json instead", category, index, rowID, category)
|
||||
}
|
||||
for field, value := range override {
|
||||
if field == "id" {
|
||||
|
||||
Reference in New Issue
Block a user