feat(zddc): embed default tree + assemble into cascade (migration phases 3-4)

Phase 3 — //go:embed all:defaults bakes the per-depth default tree into the
binary; EmbeddedPolicyTree() loads it (LoadPolicyTreeFromFS, generalized to any
fs.FS — embed, disk, or zip).

Phase 4 — PolicyTree.Assemble() folds the flat per-depth tree into the single
nested paths:-bearing ZddcFile the cascade walker already consumes, so the
walker is UNCHANGED. EmbeddedDefaults() now sources from the tree via Assemble()
instead of parsing defaults.zddc.yaml.

Proven behavior-preserving: TestEmbeddedTreeMatchesYAML asserts Assemble(tree)
deep-equals the legacy parsed defaults.zddc.yaml, and the Layer-2 matrix +
full suite stay green. defaults.zddc.yaml is kept only as that test's oracle
(deleted in phase 6). This same Assemble path is what an operator .zddc.zip
mounted at any level will use next (phase 5).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZDDC 2026-06-05 11:22:59 -05:00
parent 7e3dbe81aa
commit 21f6883157
3 changed files with 108 additions and 24 deletions

View file

@ -1,40 +1,68 @@
package zddc package zddc
import ( import (
_ "embed" "embed"
"sync" "sync"
) )
// defaultsBytes is the embedded baseline .zddc — see defaults.zddc.yaml // defaultsBytes is the legacy single-file embedded baseline. Retained only so
// for the source-of-truth and a description of its role in the cascade. // TestEmbeddedTreeMatchesYAML can prove the per-depth tree (the new source of
// truth) assembles to exactly the same ZddcFile. Removed once that guarantee
// is locked. (Still surfaced by EmbeddedDefaultsBytes / show-defaults for now.)
// //
//go:embed defaults.zddc.yaml //go:embed defaults.zddc.yaml
var defaultsBytes []byte var defaultsBytes []byte
// EmbeddedDefaultsBytes returns the raw embedded defaults YAML. // defaultsTreeFS is the embedded per-depth default policy tree — the source of
// truth. `all:` includes the `.zddc` (dot) files and `_any_` (underscore)
// directories that a bare //go:embed would skip.
// //
// Surface: the show-defaults CLI subcommand dumps these bytes to //go:embed all:defaults
// stdout so operators can copy them into <ZDDC_ROOT>/.zddc and edit. var defaultsTreeFS embed.FS
// EmbeddedDefaultsBytes returns the raw embedded defaults YAML. Surface: the
// show-defaults CLI dumps these to stdout.
func EmbeddedDefaultsBytes() []byte { func EmbeddedDefaultsBytes() []byte {
out := make([]byte, len(defaultsBytes)) out := make([]byte, len(defaultsBytes))
copy(out, defaultsBytes) copy(out, defaultsBytes)
return out return out
} }
var (
embeddedTreeOnce sync.Once
embeddedTree PolicyTree
embeddedTreeErr error
)
// EmbeddedPolicyTree returns the baked-in per-depth default policy tree,
// memoised. This is the embedded form of the .zddc.zip mounted at the
// deployment root (the bottom of every cascade).
func EmbeddedPolicyTree() (PolicyTree, error) {
embeddedTreeOnce.Do(func() {
embeddedTree, embeddedTreeErr = LoadPolicyTreeFromFS(defaultsTreeFS, "defaults")
})
return embeddedTree, embeddedTreeErr
}
var ( var (
embeddedDefaultsOnce sync.Once embeddedDefaultsOnce sync.Once
embeddedDefaults ZddcFile embeddedDefaults ZddcFile
embeddedDefaultsErr error embeddedDefaultsErr error
) )
// EmbeddedDefaults returns the parsed embedded defaults ZddcFile, // EmbeddedDefaults returns the embedded defaults assembled from the per-depth
// memoised. Parse errors surface on the first call and are sticky. // tree into the single nested ZddcFile the cascade walker consumes, memoised.
// //
// The cascade walker (EffectivePolicy) consults this as the bottom- // The cascade walker (EffectivePolicy) consults this as the bottom-most level
// most level unless an on-disk .zddc up the chain sets `inherit: false`. // unless an on-disk .zddc up the chain sets `inherit: false`.
func EmbeddedDefaults() (ZddcFile, error) { func EmbeddedDefaults() (ZddcFile, error) {
embeddedDefaultsOnce.Do(func() { embeddedDefaultsOnce.Do(func() {
embeddedDefaults, embeddedDefaultsErr = parseBytes(defaultsBytes) tree, err := EmbeddedPolicyTree()
if err != nil {
embeddedDefaultsErr = err
return
}
embeddedDefaults = tree.Assemble()
}) })
return embeddedDefaults, embeddedDefaultsErr return embeddedDefaults, embeddedDefaultsErr
} }

View file

@ -3,7 +3,7 @@ package zddc
import ( import (
"io/fs" "io/fs"
"os" "os"
"path/filepath" "path"
"strings" "strings"
) )
@ -88,25 +88,58 @@ func (t PolicyTree) Along(relSegs []string) []ZddcFile {
return out return out
} }
// LoadPolicyTreeFromDir loads a per-depth .zddc tree from fsDir, mapping the // Assemble folds a flat per-depth tree into a single nested ZddcFile whose
// AnyPlaceholder directory to the "*" wildcard. Keys are member dirs relative // Paths map mirrors the tree — the inverse of authoring policy as per-depth
// to fsDir ("" for fsDir/.zddc). Used for the embedded default-tree source. // files. The result is exactly what a single nested-paths: .zddc would parse
func LoadPolicyTreeFromDir(fsDir string) (PolicyTree, error) { // to, so the cascade walker consumes it unchanged: the embedded defaults and
// any operator .zddc.zip both become ordinary paths:-bearing ZddcFiles.
func (t PolicyTree) Assemble() ZddcFile {
return assembleTree(map[string]ZddcFile(t))
}
// assembleTree recursively builds the nested node for a set of members keyed by
// path relative to this node ("" = this node's own content; "head/rest" = a
// descendant). Children are grouped by first segment and recursed into Paths.
func assembleTree(members map[string]ZddcFile) ZddcFile {
node := members[""]
groups := map[string]map[string]ZddcFile{}
for key, zf := range members {
if key == "" {
continue
}
head, rest, _ := strings.Cut(key, "/")
if groups[head] == nil {
groups[head] = map[string]ZddcFile{}
}
groups[head][rest] = zf
}
if len(groups) > 0 {
node.Paths = make(map[string]ZddcFile, len(groups))
for head, sub := range groups {
node.Paths[head] = assembleTree(sub)
}
}
return node
}
// LoadPolicyTreeFromFS loads a per-depth .zddc tree rooted at `root` within
// fsys, mapping the AnyPlaceholder directory to the "*" wildcard. Keys are
// member dirs relative to root ("" for root/.zddc). Works on an embed.FS, an
// os.DirFS, or a zip's fs.FS.
func LoadPolicyTreeFromFS(fsys fs.FS, root string) (PolicyTree, error) {
out := PolicyTree{} out := PolicyTree{}
err := filepath.WalkDir(fsDir, func(p string, d fs.DirEntry, walkErr error) error { err := fs.WalkDir(fsys, root, func(p string, d fs.DirEntry, walkErr error) error {
if walkErr != nil { if walkErr != nil {
return walkErr return walkErr
} }
if d.IsDir() || d.Name() != ".zddc" { if d.IsDir() || d.Name() != ".zddc" {
return nil return nil
} }
rel, err := filepath.Rel(fsDir, filepath.Dir(p)) rel := strings.TrimPrefix(path.Dir(p), root)
if err != nil { rel = strings.Trim(rel, "/")
return err
}
key := "" key := ""
if rel != "." { if rel != "" {
parts := strings.Split(filepath.ToSlash(rel), "/") parts := strings.Split(rel, "/")
for i, s := range parts { for i, s := range parts {
if s == AnyPlaceholder { if s == AnyPlaceholder {
parts[i] = "*" parts[i] = "*"
@ -114,7 +147,7 @@ func LoadPolicyTreeFromDir(fsDir string) (PolicyTree, error) {
} }
key = strings.Join(parts, "/") key = strings.Join(parts, "/")
} }
data, err := os.ReadFile(p) data, err := fs.ReadFile(fsys, p)
if err != nil { if err != nil {
return err return err
} }
@ -130,3 +163,8 @@ func LoadPolicyTreeFromDir(fsDir string) (PolicyTree, error) {
} }
return out, nil return out, nil
} }
// LoadPolicyTreeFromDir is the on-disk convenience over LoadPolicyTreeFromFS.
func LoadPolicyTreeFromDir(fsDir string) (PolicyTree, error) {
return LoadPolicyTreeFromFS(os.DirFS(fsDir), ".")
}

View file

@ -119,6 +119,24 @@ func TestPolicyTreeAlong(t *testing.T) {
} }
} }
// Phase-4 gate: the embedded per-depth tree assembles to EXACTLY the legacy
// defaults.zddc.yaml ZddcFile, so pointing EmbeddedDefaults at the tree is a
// behavioral no-op. (The Layer-2 matrix is the decision-level confirmation.)
func TestEmbeddedTreeMatchesYAML(t *testing.T) {
tree, err := EmbeddedPolicyTree()
if err != nil {
t.Fatalf("embedded tree: %v", err)
}
assembled := tree.Assemble()
legacy, err := parseBytes(defaultsBytes)
if err != nil {
t.Fatalf("parse legacy yaml: %v", err)
}
if !reflect.DeepEqual(assembled, legacy) {
t.Errorf("assembled per-depth tree != legacy defaults.zddc.yaml\n assembled=%+v\n legacy=%+v", assembled, legacy)
}
}
func keysOf(t PolicyTree) []string { func keysOf(t PolicyTree) []string {
out := make([]string, 0, len(t)) out := make([]string, 0, len(t))
for k := range t { for k := range t {