First step of the .zddc-first-configuration rollout: pure plumbing
that makes the future move-everything-out-of-Go work mechanically
possible without changing any current behaviour.
New pieces:
1. zddc/internal/zddc/defaults.zddc.yaml — a real YAML file in the
repo. Single source of truth for the baked-in baseline; intentionally
minimal in Phase 1 (just title + empty acl) so existing deployments
stay bit-identical until Phase 2 starts populating the schema.
2. //go:embed (defaults.go) bakes the bytes into the binary so
shipped deployments don't need the file. Operators who want a
starting point export with:
zddc-server show-defaults > /var/lib/zddc/root/.zddc
3. PolicyChain gains an Embedded ZddcFile field. EffectivePolicy
layers in the embedded defaults as a baseline below the on-disk
chain. Consumers that want the full effective view consult both;
existing consumers that only read chain.Levels keep working
bit-identically (the new field is additive).
4. New top-level `inherit:` key on ZddcFile. Default true. Set
`inherit: false` on any on-disk .zddc to zero out chain.Embedded
— the operator owns every rule from that level outward. Useful at
the on-disk root to fully reject the embedded defaults; useful at
deeper levels for sandbox subtrees.
5. `zddc-server show-defaults` (also accepts --show-defaults) subcommand
dumps the embedded bytes to stdout — same shape as --print-rego.
No flag plumbing needed beyond the existing args walk.
6. Tests: parse-roundtrip on the embedded file, presence in chain by
default, inherit:false drops it, explicit inherit:true is a no-op
versus the default.
Phase 2 (next): add a `paths:` recursive map + `default_tool:` /
`auto_own:` / `virtual:` keys, populate defaults.zddc.yaml with the
canonical ZDDC convention, and migrate apps.DefaultAppAt /
AutoOwnCanonicalNames / VirtualOnlyCanonicalNames to cascade lookups.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
100 lines
2.9 KiB
Go
100 lines
2.9 KiB
Go
package zddc
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestEmbeddedDefaultsParse — the shipped defaults.zddc.yaml must
|
|
// parse cleanly into a ZddcFile. Regression guard against accidental
|
|
// YAML syntax errors in the source-of-truth file.
|
|
func TestEmbeddedDefaultsParse(t *testing.T) {
|
|
zf, err := EmbeddedDefaults()
|
|
if err != nil {
|
|
t.Fatalf("EmbeddedDefaults: %v", err)
|
|
}
|
|
if zf.Title == "" {
|
|
t.Errorf("embedded defaults have no title")
|
|
}
|
|
}
|
|
|
|
// TestEmbeddedDefaultsBytesDumpable — the bytes used by the show-
|
|
// defaults CLI must be non-empty and start with a comment so an
|
|
// operator pasting them into a real file sees the header.
|
|
func TestEmbeddedDefaultsBytesDumpable(t *testing.T) {
|
|
got := EmbeddedDefaultsBytes()
|
|
if len(got) == 0 {
|
|
t.Fatal("EmbeddedDefaultsBytes returned empty slice")
|
|
}
|
|
if !strings.HasPrefix(strings.TrimLeft(string(got), " \t"), "#") {
|
|
t.Errorf("expected leading comment, got: %q", string(got[:60]))
|
|
}
|
|
}
|
|
|
|
// TestCascadeIncludesEmbeddedByDefault — a fresh deployment with no
|
|
// on-disk .zddc still gets the embedded defaults reachable via
|
|
// chain.Embedded.
|
|
func TestCascadeIncludesEmbeddedByDefault(t *testing.T) {
|
|
resetCache()
|
|
root := t.TempDir()
|
|
leaf := filepath.Join(root, "Proj")
|
|
if err := mkdirAll(leaf); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
chain, err := EffectivePolicy(root, leaf)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if chain.Embedded.Title == "" {
|
|
t.Errorf("chain.Embedded.Title empty, want defaults title to populate")
|
|
}
|
|
}
|
|
|
|
// TestCascadeInheritFalseDropsEmbedded — when an on-disk .zddc sets
|
|
// top-level `inherit: false`, the embedded layer is zeroed out.
|
|
func TestCascadeInheritFalseDropsEmbedded(t *testing.T) {
|
|
resetCache()
|
|
root := t.TempDir()
|
|
writeZddc(t, root, "title: 'op-managed'\ninherit: false\n")
|
|
leaf := filepath.Join(root, "Proj")
|
|
if err := mkdirAll(leaf); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
chain, err := EffectivePolicy(root, leaf)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if chain.Embedded.Title != "" {
|
|
t.Errorf("chain.Embedded.Title = %q, want empty (inherit:false should drop embedded)",
|
|
chain.Embedded.Title)
|
|
}
|
|
// On-disk level still present.
|
|
if got := chain.Levels[0].Title; got != "op-managed" {
|
|
t.Errorf("Levels[0].Title = %q, want %q", got, "op-managed")
|
|
}
|
|
}
|
|
|
|
// TestCascadeInheritTrueExplicitKeepsEmbedded — `inherit: true`
|
|
// explicitly is the same as omitting it (default behaviour).
|
|
func TestCascadeInheritTrueExplicitKeepsEmbedded(t *testing.T) {
|
|
resetCache()
|
|
root := t.TempDir()
|
|
writeZddc(t, root, "title: 'op-managed'\ninherit: true\n")
|
|
leaf := filepath.Join(root, "Proj")
|
|
if err := mkdirAll(leaf); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
chain, err := EffectivePolicy(root, leaf)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if chain.Embedded.Title == "" {
|
|
t.Errorf("chain.Embedded.Title empty, want defaults to remain since inherit: true is the default")
|
|
}
|
|
}
|
|
|
|
func mkdirAll(p string) error {
|
|
return os.MkdirAll(p, 0o755)
|
|
}
|