From a471de8788b7dc245c13cc1f3602f146879ebeb2 Mon Sep 17 00:00:00 2001 From: ZDDC Date: Thu, 7 May 2026 08:20:34 -0500 Subject: [PATCH] refactor(zddc): extract writeAutoOwnZddc into zddc.WriteAutoOwnZddc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pure refactor. The mkdir post-hook in handler/fileapi.go duplicated zddc-package types; lifting the body into the package itself lets the upcoming EnsureCanonicalAncestors helper share it without re-exposing the file API's internals. No behaviour change. The grant shape (creator email → rwcda + CreatedBy audit field) and the atomic-write path through zddc.WriteFile are unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) --- zddc/internal/handler/fileapi.go | 15 +-------------- zddc/internal/zddc/special.go | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/zddc/internal/handler/fileapi.go b/zddc/internal/handler/fileapi.go index 1888ff8..15407fc 100644 --- a/zddc/internal/handler/fileapi.go +++ b/zddc/internal/handler/fileapi.go @@ -526,7 +526,7 @@ func serveFileMkdir(cfg config.Config, w http.ResponseWriter, r *http.Request) { if email := EmailFromContext(r); email != "" { parentName := filepath.Base(filepath.Dir(abs)) if zddc.IsAutoOwnParent(parentName) { - if err := writeAutoOwnZddc(abs, email); err != nil { + if err := zddc.WriteAutoOwnZddc(abs, email); err != nil { slog.Warn("auto-own .zddc write failed", "path", abs, "err", err) } } @@ -537,19 +537,6 @@ func serveFileMkdir(cfg config.Config, w http.ResponseWriter, r *http.Request) { auditFile(r, "mkdir", cleanURL, http.StatusCreated, 0, nil) } -// writeAutoOwnZddc serializes a creator-grant .zddc into newDir. -// Marshals via the same yaml encoder ParseFile reads (round-trip -// guaranteed) and writes atomically via zddc.WriteAtomic. -func writeAutoOwnZddc(newDir, email string) error { - zf := zddc.ZddcFile{ - ACL: zddc.ACLRules{ - Permissions: map[string]string{email: "rwcda"}, - }, - CreatedBy: email, - } - return zddc.WriteFile(newDir, zf) -} - // auditFile emits a structured log line for each file API operation. // AccessLogMiddleware already logs every request — this adds an // op-tagged line so audit consumers can filter by operation without diff --git a/zddc/internal/zddc/special.go b/zddc/internal/zddc/special.go index df13b7a..aea7715 100644 --- a/zddc/internal/zddc/special.go +++ b/zddc/internal/zddc/special.go @@ -71,6 +71,28 @@ var AutoOwnFolderNames = []string{"Incoming", "Working", "Staging"} // Deprecated: use PartyFolders + IsWormPath. var WormFolderNames = []string{"Issued", "Received"} +// WriteAutoOwnZddc serialises a creator-grant .zddc into dir, granting +// principalEmail rwcda and recording it in CreatedBy. Used by the file +// API's mkdir post-hook (and by EnsureCanonicalAncestors) to seed +// ownership when a new auto-own folder is materialised. +// +// The grant is identical to what an operator would write by hand — +// direct email pattern, "rwcda" verb set — so the creator can later +// edit the file normally to add collaborators. +// +// Atomic: marshals via the same yaml encoder ParseFile reads +// (round-trip guaranteed) and writes via zddc.WriteFile (which +// performs an atomic temp-write + rename via zddc.WriteAtomic). +func WriteAutoOwnZddc(dir, principalEmail string) error { + zf := ZddcFile{ + ACL: ACLRules{ + Permissions: map[string]string{principalEmail: "rwcda"}, + }, + CreatedBy: principalEmail, + } + return WriteFile(dir, zf) +} + // ResolveCanonical returns the on-disk name of the canonical folder // 'logical' (lowercase) inside parentDir, or "" if no case variant // exists. Caller decides whether to MkdirAll(parentDir+"/"+logical)