Phase 5: scaffold Crucible (sow-tools) — dispatcher + builder shims, container, PR-first CI, fail-closed (D11, D19).
This commit is contained in:
@@ -1,190 +0,0 @@
|
||||
package topdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func isNullLike(value any) bool {
|
||||
if value == nil {
|
||||
return true
|
||||
}
|
||||
text, ok := value.(string)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return strings.TrimSpace(text) == nullValue
|
||||
}
|
||||
|
||||
func normalizeWikiMetadataKey(key string) string {
|
||||
switch normalizeMetadataKey(key) {
|
||||
case "reqfeats", "req_feats":
|
||||
return "reqfeats"
|
||||
case "generate", "wikigenerate":
|
||||
return "generate"
|
||||
case "canonical", "wikicanonical":
|
||||
return "canonical"
|
||||
case "status", "wikistatus":
|
||||
return "status"
|
||||
case "progressionnotes", "progression_notes", "classprogressionnotes", "class_progression_notes":
|
||||
return "progression_notes"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func legacyWikiMetadataField(key string) string {
|
||||
switch normalizeMetadataKey(key) {
|
||||
case "wikireqfeats":
|
||||
return "reqfeats"
|
||||
case "wikigenerate":
|
||||
return "generate"
|
||||
case "wikicanonical":
|
||||
return "canonical"
|
||||
case "wikistatus":
|
||||
return "status"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func parseWikiMetadata(raw any) (map[string]any, error) {
|
||||
obj, ok := raw.(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("must be an object")
|
||||
}
|
||||
wiki := map[string]any{}
|
||||
for key, value := range obj {
|
||||
normalized := normalizeWikiMetadataKey(key)
|
||||
if normalized == "" {
|
||||
return nil, fmt.Errorf("unknown field %q", key)
|
||||
}
|
||||
if isNullLike(value) {
|
||||
continue
|
||||
}
|
||||
wiki[normalized] = cloneAuthoringValue(value)
|
||||
}
|
||||
return wiki, nil
|
||||
}
|
||||
|
||||
func mergeMetadataMaps(base, overlay map[string]any) map[string]any {
|
||||
if len(base) == 0 && len(overlay) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := map[string]any{}
|
||||
for key, value := range base {
|
||||
out[key] = cloneAuthoringValue(value)
|
||||
}
|
||||
for key, value := range overlay {
|
||||
baseValue, hasBase := out[key]
|
||||
baseMap, baseIsMap := baseValue.(map[string]any)
|
||||
valueMap, valueIsMap := value.(map[string]any)
|
||||
if hasBase && baseIsMap && valueIsMap {
|
||||
out[key] = mergeMetadataMaps(baseMap, valueMap)
|
||||
continue
|
||||
}
|
||||
out[key] = cloneAuthoringValue(value)
|
||||
}
|
||||
if len(out) == 0 {
|
||||
return nil
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func parseExistingMetadata(row map[string]any) (map[string]any, error) {
|
||||
rawMeta, ok := lookupField(row, "meta")
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
return parseRowMetadata(rawMeta)
|
||||
}
|
||||
|
||||
func setLegacyWikiMetadata(row map[string]any, field string, value any) error {
|
||||
wikiKey := legacyWikiMetadataField(field)
|
||||
if wikiKey == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
meta, err := parseExistingMetadata(row)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if meta == nil {
|
||||
meta = map[string]any{}
|
||||
}
|
||||
|
||||
wiki, _ := meta["wiki"].(map[string]any)
|
||||
if wiki == nil {
|
||||
wiki = map[string]any{}
|
||||
}
|
||||
|
||||
if isNullLike(value) {
|
||||
delete(wiki, wikiKey)
|
||||
} else {
|
||||
wiki[wikiKey] = cloneAuthoringValue(value)
|
||||
}
|
||||
|
||||
if len(wiki) == 0 {
|
||||
delete(meta, "wiki")
|
||||
} else {
|
||||
meta["wiki"] = wiki
|
||||
}
|
||||
if len(meta) == 0 {
|
||||
delete(row, "meta")
|
||||
} else {
|
||||
row["meta"] = meta
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func canonicalizeWikiMetadataDocument(obj map[string]any) {
|
||||
if rawColumns, ok := obj["columns"].([]any); ok {
|
||||
filtered := make([]any, 0, len(rawColumns))
|
||||
for _, raw := range rawColumns {
|
||||
column, ok := raw.(string)
|
||||
if ok && isAuthoringOnlyField(column) {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, raw)
|
||||
}
|
||||
obj["columns"] = filtered
|
||||
}
|
||||
if rows, ok := obj["rows"].([]any); ok {
|
||||
for _, raw := range rows {
|
||||
row, ok := raw.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
canonicalizeWikiMetadataRow(row)
|
||||
}
|
||||
}
|
||||
if entries, ok := obj["entries"].(map[string]any); ok {
|
||||
for _, raw := range entries {
|
||||
row, ok := raw.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
canonicalizeWikiMetadataRow(row)
|
||||
}
|
||||
}
|
||||
if overrides, ok := obj["overrides"].([]any); ok {
|
||||
for _, raw := range overrides {
|
||||
row, ok := raw.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
canonicalizeWikiMetadataRow(row)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func canonicalizeWikiMetadataRow(row map[string]any) {
|
||||
for _, field := range []string{"WIKIREQFEATS", "WIKIGENERATE", "WIKICANONICAL", "WIKISTATUS"} {
|
||||
value, ok := row[field]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
_ = setLegacyWikiMetadata(row, field, value)
|
||||
delete(row, field)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user