From d00afa1ddc0664dee6ad98a53e0ffc3c657d59ac Mon Sep 17 00:00:00 2001 From: ZDDC Date: Thu, 28 May 2026 12:48:49 -0500 Subject: [PATCH] fix(server): carry history through the paths-tree merge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mergeOverlay (used to thread embedded defaults' paths: tree into chain levels) didn't copy the new History *bool, so EffectiveHistory never saw history: true on archive//working/ — the feature would have silently never triggered. Add the field to the overlay and a HistoryAt defaults test that exercises the real cascade (working/ + fenced homes true; sibling slots false). Co-Authored-By: Claude Opus 4.7 (1M context) --- zddc/internal/zddc/lookups_test.go | 28 ++++++++++++++++++++++++++++ zddc/internal/zddc/walker.go | 3 +++ 2 files changed, 31 insertions(+) diff --git a/zddc/internal/zddc/lookups_test.go b/zddc/internal/zddc/lookups_test.go index 29f7ffa..028f463 100644 --- a/zddc/internal/zddc/lookups_test.go +++ b/zddc/internal/zddc/lookups_test.go @@ -48,6 +48,34 @@ func TestDefaultToolAt_FromEmbeddedConvention(t *testing.T) { } } +// TestHistoryAt_Defaults — the embedded convention enables edit-history +// versioning on archive//working/ and (because history is +// subtree-inheriting and ignores the auto_own_fenced homes' inherit:false) +// on the per-user homes and any depth beneath them. Sibling slots do not +// get history. +func TestHistoryAt_Defaults(t *testing.T) { + resetCache() + root := t.TempDir() + cases := []struct { + path string + want bool + }{ + {filepath.Join(root, "Project-X", "archive", "Acme", "working"), true}, + {filepath.Join(root, "Project-X", "archive", "Acme", "working", "alice@example.com"), true}, + {filepath.Join(root, "Project-X", "archive", "Acme", "working", "alice@example.com", "notes"), true}, + {filepath.Join(root, "Project-X", "archive", "Acme", "mdl"), false}, + {filepath.Join(root, "Project-X", "archive", "Acme", "incoming"), false}, + {filepath.Join(root, "Project-X", "archive", "Acme", "staging"), false}, + {filepath.Join(root, "Project-X", "archive", "Acme", "received"), false}, + {filepath.Join(root, "Project-X", "archive"), false}, + } + for _, tc := range cases { + if got := HistoryAt(root, tc.path); got != tc.want { + t.Errorf("HistoryAt(%q) = %v, want %v", tc.path[len(root):], got, tc.want) + } + } +} + // TestDirToolAt — the trailing-slash form floors at "browse" for // every path (the embedded convention sets dir_tool nowhere), and an // on-disk .zddc can override it for a subtree. diff --git a/zddc/internal/zddc/walker.go b/zddc/internal/zddc/walker.go index a4e84a5..a0328b3 100644 --- a/zddc/internal/zddc/walker.go +++ b/zddc/internal/zddc/walker.go @@ -88,6 +88,9 @@ func mergeOverlay(base, top ZddcFile) ZddcFile { if top.DropTarget != nil { out.DropTarget = top.DropTarget } + if top.History != nil { + out.History = top.History + } // Worm: presence (non-nil, even empty) marks the WORM zone. // Concat-dedupe across levels (a deeper .zddc adds controllers); // preserve a non-nil empty slice so `worm: []` survives the