Completes the migration. The embedded per-depth tree (internal/zddc/defaults/)
is now the sole source of the shipped baseline; defaults.zddc.yaml is deleted.
- EmbeddedDefaults() assembles the tree (no yaml). show-defaults now emits a
.zddc.zip (per-depth, "*" wildcard members) via EmbeddedDefaultsZip() —
operators redirect it to <ROOT>/.zddc.zip (or any directory) and edit/add/
delete individual members.
- Dropped EmbeddedDefaultsBytes; reworked the dumpable test to validate the
emitted zip; removed the now-redundant tree-vs-yaml oracle (the Layer-2
matrix is the ongoing behavioral guarantee, and it stays green).
- Swept stale "defaults.zddc.yaml" comment references to the embedded tree.
- GRAMMAR.md §1/§6 updated: .zddc.zip is a policy bundle mountable at ANY
directory (subtree mount; inherit:false + acl.inherit:false = island); the
shipped baseline is the embedded bundle at the root.
Net of the 6-phase migration: policy is per-depth .zddc files in a .zddc.zip
that an operator can drop at any level to override the cascade; the engine
(Assemble + the unchanged walker) enforces it. Full Go suite + matrix green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
128 lines
4.6 KiB
Go
128 lines
4.6 KiB
Go
package zddc
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
// resolver matching mechanics: literal beats "*", length must match, most-
|
|
// specific wins, empty path → root.
|
|
func TestPolicyTreeResolve(t *testing.T) {
|
|
tree := PolicyTree{
|
|
"": {Title: "root"},
|
|
"*": {Title: "project"},
|
|
"working": {Title: "literal-working"},
|
|
"*/working": {Title: "any-working"},
|
|
"*/working/*": {Title: "any-working-any"},
|
|
"*/mdl": {Title: "any-mdl"},
|
|
"Proj/working": {Title: "proj-working"},
|
|
}
|
|
cases := []struct {
|
|
segs []string
|
|
want string // resolved key
|
|
ok bool
|
|
}{
|
|
{nil, "", true}, // root
|
|
{[]string{"Proj"}, "*", true}, // no literal "Proj" at level 1 → "*"
|
|
{[]string{"working"}, "working", true}, // literal beats "*"
|
|
{[]string{"Proj", "working"}, "Proj/working", true}, // both literal beats "*/working"
|
|
{[]string{"Other", "working"}, "*/working", true}, // first seg "*", second literal
|
|
{[]string{"Proj", "mdl"}, "*/mdl", true},
|
|
{[]string{"Proj", "working", "Acme"}, "*/working/*", true},
|
|
{[]string{"Proj", "nope"}, "", false}, // no len-2 key matches "*/nope"
|
|
}
|
|
for _, c := range cases {
|
|
got, ok := tree.resolveTreeDir(c.segs)
|
|
if ok != c.ok || (ok && got != c.want) {
|
|
t.Errorf("resolveTreeDir(%v) = (%q,%v), want (%q,%v)", c.segs, got, ok, c.want, c.ok)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load the real embedded-default source tree and assert the split content
|
|
// reproduces the intended document-control policy (the faithfulness check at
|
|
// the data level; full effective-policy parity is the Layer-2 matrix once the
|
|
// cascade is wired in Phase 4).
|
|
func TestLoadPolicyTreeFromDir(t *testing.T) {
|
|
tree, err := LoadPolicyTreeFromDir("defaults")
|
|
if err != nil {
|
|
t.Fatalf("load defaults tree: %v", err)
|
|
}
|
|
wantKeys := []string{
|
|
"", "*", "*/archive", "*/ssr", "*/incoming", "*/incoming/*",
|
|
"*/reviewing", "*/reviewing/*", "*/working", "*/working/*",
|
|
"*/staging", "*/staging/*", "*/mdl", "*/mdl/*", "*/rsk", "*/rsk/*",
|
|
}
|
|
for _, k := range wantKeys {
|
|
if _, ok := tree[k]; !ok {
|
|
t.Errorf("missing tree key %q", k)
|
|
}
|
|
}
|
|
if len(tree) != len(wantKeys) {
|
|
t.Errorf("tree has %d keys, want %d: %v", len(tree), len(wantKeys), keysOf(tree))
|
|
}
|
|
|
|
// Spot-check the policy each member carries.
|
|
if got := tree[""].AvailableTools; !reflect.DeepEqual(got, []string{"archive", "browse", "landing"}) {
|
|
t.Errorf("root available_tools = %v", got)
|
|
}
|
|
if _, ok := tree[""].Roles["document_controller"]; !ok {
|
|
t.Errorf("root missing document_controller role")
|
|
}
|
|
if got := tree["*"].ACL.Permissions["project_team"]; got != "r" {
|
|
t.Errorf("project-level project_team = %q, want r", got)
|
|
}
|
|
if got := tree["*"].ACL.Permissions["document_controller"]; got != "rw" {
|
|
t.Errorf("project-level document_controller = %q, want rw", got)
|
|
}
|
|
if got := tree["*/working"].ACL.Permissions["document_controller"]; got != "rwcda" {
|
|
t.Errorf("working document_controller = %q, want rwcda", got)
|
|
}
|
|
if got := tree["*/working"].ACL.Permissions["project_team"]; got != "cr" {
|
|
t.Errorf("working project_team = %q, want cr", got)
|
|
}
|
|
if tree["*/working"].PartySource != "ssr" {
|
|
t.Errorf("working party_source = %q, want ssr", tree["*/working"].PartySource)
|
|
}
|
|
if h := tree["*/working"].History; h == nil || !*h {
|
|
t.Errorf("working history not true")
|
|
}
|
|
if got := tree["*/mdl"].ACL.Permissions["project_team"]; got != "rwc" {
|
|
t.Errorf("mdl project_team = %q, want rwc", got)
|
|
}
|
|
if got := tree["*/archive"].Worm; !reflect.DeepEqual(got, []string{"document_controller"}) {
|
|
t.Errorf("archive worm = %v, want [document_controller]", got)
|
|
}
|
|
if ao := tree["*/working/*"].AutoOwn; ao == nil || !*ao {
|
|
t.Errorf("working/<party> auto_own not true")
|
|
}
|
|
}
|
|
|
|
// Along returns the governing members in root→leaf order for a path.
|
|
func TestPolicyTreeAlong(t *testing.T) {
|
|
tree, err := LoadPolicyTreeFromDir("defaults")
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
levels := tree.Along([]string{"Proj", "working", "Acme"})
|
|
// "", "*", "*/working", "*/working/*" → 4 contributing levels.
|
|
if len(levels) != 4 {
|
|
t.Fatalf("Along returned %d levels, want 4", len(levels))
|
|
}
|
|
// Leaf level is working/<party>: auto_own.
|
|
if ao := levels[3].AutoOwn; ao == nil || !*ao {
|
|
t.Errorf("leaf level should carry auto_own")
|
|
}
|
|
// The working level grants the DC full authority.
|
|
if got := levels[2].ACL.Permissions["document_controller"]; got != "rwcda" {
|
|
t.Errorf("level 2 (working) document_controller = %q, want rwcda", got)
|
|
}
|
|
}
|
|
|
|
func keysOf(t PolicyTree) []string {
|
|
out := make([]string, 0, len(t))
|
|
for k := range t {
|
|
out = append(out, k)
|
|
}
|
|
return out
|
|
}
|