chore(zddc): remove dead canonical-folder predicates
Phase 3 retired these symbols by migrating their consumers to the .zddc cascade lookups. Removing them now that nothing references them: - var zddc.ProjectRootFolders - var zddc.PartyFolders - var zddc.AutoOwnCanonicalNames - var zddc.VirtualOnlyCanonicalNames - func zddc.IsProjectRootFolder - func zddc.IsArchivePartyFolder - func zddc.IsArchivePartyMdlDir - func handler.isArchivePartyDir The canonical convention is expressed in defaults.zddc.yaml and consulted via lookups.go's DefaultToolAt / AutoOwnAt / VirtualAt / IsDeclaredPath / ChildrenDeclaredAt / AvailableToolsAt / IsToolAvailableAt. Operators override per-directory via on-disk .zddc files; the embedded layer is the documented baseline. Test removals: - TestCanonicalLists (lists no longer exist) - TestIsProjectRootFolder (function no longer exists) Equivalent coverage lives in lookups_test.go's TestDefaultToolAt_FromEmbeddedConvention, TestIsDeclaredPath_FromEmbeddedConvention, etc. — which assert the convention via the cascade's actual lookup path rather than the predicates' return values. handler.isAtArchivePartyMdlDir is RETAINED — it's still actively consumed by RecognizeTableRequest's default-MDL fallback in table.html URL resolution. That's a tighter file-path predicate than the cascade walker would naturally express; can revisit if it ever needs to become configurable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5e393cbeaf
commit
6310afa922
5 changed files with 12 additions and 211 deletions
|
|
@ -260,24 +260,6 @@ func isNotExistError(err error) bool {
|
|||
return err != nil && strings.Contains(err.Error(), "no such file or directory")
|
||||
}
|
||||
|
||||
// isArchivePartyDir reports whether dirAbs is a <project>/archive/<party>/
|
||||
// directory under fsRoot, with archive case-folded.
|
||||
func isArchivePartyDir(fsRoot, dirAbs string) bool {
|
||||
rel, err := filepath.Rel(fsRoot, dirAbs)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
rel = filepath.ToSlash(rel)
|
||||
if strings.HasPrefix(rel, "../") || rel == ".." || rel == "." {
|
||||
return false
|
||||
}
|
||||
parts := strings.Split(rel, "/")
|
||||
if len(parts) != 3 {
|
||||
return false
|
||||
}
|
||||
return strings.EqualFold(parts[1], "archive")
|
||||
}
|
||||
|
||||
// ServeTable serves the static tables.html bytes for a recognized
|
||||
// request. ACL gate is the read action at the request directory; on
|
||||
// allow, the embedded HTML is written verbatim. The client takes over
|
||||
|
|
|
|||
|
|
@ -1300,7 +1300,7 @@ body.help-open .app-header {
|
|||
</svg>
|
||||
<div class="header-title-group">
|
||||
<span class="app-header__title" id="table-title">ZDDC Table</span>
|
||||
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.17-alpha · 2026-05-11 20:31:34 · 9d18047-dirty</span></span>
|
||||
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.17-alpha · 2026-05-11 21:01:13 · 5e393cb-dirty</span></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
|
|
|
|||
|
|
@ -35,9 +35,6 @@ func DefaultToolAt(fsRoot, dirPath string) string {
|
|||
// propagate to descendants (creating working/alice/notes/sub/ does
|
||||
// not auto-own sub/; only the explicitly-declared per-user home is
|
||||
// auto-owned).
|
||||
//
|
||||
// Replaces AutoOwnCanonicalNames once the file API's mkdir hook is
|
||||
// migrated.
|
||||
func AutoOwnAt(fsRoot, dirPath string) bool {
|
||||
chain, err := EffectivePolicy(fsRoot, dirPath)
|
||||
if err != nil {
|
||||
|
|
@ -75,8 +72,6 @@ func AutoOwnFencedAt(fsRoot, dirPath string) bool {
|
|||
// purely virtual (never materialise on disk). Leaf-only: the virtual
|
||||
// property describes a particular path, not a subtree. A child of a
|
||||
// virtual directory is not automatically virtual itself.
|
||||
//
|
||||
// Replaces VirtualOnlyCanonicalNames once consumers are migrated.
|
||||
func VirtualAt(fsRoot, dirPath string) bool {
|
||||
chain, err := EffectivePolicy(fsRoot, dirPath)
|
||||
if err != nil {
|
||||
|
|
@ -100,9 +95,6 @@ func VirtualAt(fsRoot, dirPath string) bool {
|
|||
// even if the directory doesn't exist on disk. Used by listing
|
||||
// fallbacks to decide whether a missing directory should return an
|
||||
// empty listing (treat as virtual) vs 404 (truly unknown).
|
||||
//
|
||||
// Replaces IsProjectRootFolder + IsArchivePartyFolder once
|
||||
// consumers are migrated.
|
||||
func IsDeclaredPath(fsRoot, dirPath string) bool {
|
||||
chain, err := EffectivePolicy(fsRoot, dirPath)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -6,133 +6,13 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// ProjectRootFolders are the canonical lowercase folder names that may
|
||||
// appear directly under a project root. The server resolves them
|
||||
// case-insensitively on disk: a manually-created Working/ is reused
|
||||
// rather than shadowed by a new working/.
|
||||
//
|
||||
// - "archive" — formal record of issued/received transmittals,
|
||||
// organised by counterparty (and ourselves)
|
||||
// - "working" — user-owned drafting workspace
|
||||
// - "staging" — outbound-transmittal preparation
|
||||
// - "reviewing" — purely virtual cross-reference of in-progress
|
||||
// review responses (never written to disk)
|
||||
var ProjectRootFolders = []string{"archive", "working", "staging", "reviewing"}
|
||||
|
||||
// PartyFolders are the canonical lowercase folder names that may appear
|
||||
// directly under archive/<party>/, where <party> is a counterparty or
|
||||
// the self-folder (we treat ourselves like any other third party).
|
||||
//
|
||||
// - "mdl" — yaml-per-deliverable metadata, edited via the
|
||||
// table-editor app at <party>/mdl.table.html
|
||||
// - "incoming" — that party's drop point (we QC then promote)
|
||||
// - "received" — immutable record of incoming we've accepted (WORM)
|
||||
// - "issued" — immutable record of what we sent (WORM)
|
||||
var PartyFolders = []string{"mdl", "incoming", "received", "issued"}
|
||||
|
||||
// AutoOwnCanonicalNames is the subset of canonical folder names where
|
||||
// the file API's first-write hook auto-writes a creator-owned .zddc
|
||||
// granting the creator rwcda. Excluded by design:
|
||||
//
|
||||
// - "archive": container only
|
||||
// - "reviewing": purely virtual, never on disk
|
||||
// - "mdl": yaml data store; ACL flows from archive/<party>/.zddc
|
||||
// - "received" / "issued": WORM — auto-own would defeat the mask
|
||||
var AutoOwnCanonicalNames = []string{"working", "staging", "incoming"}
|
||||
|
||||
// VirtualOnlyCanonicalNames is the subset of canonical folder names
|
||||
// that are never materialised on disk by the auto-create hooks. The
|
||||
// server treats requests under these prefixes as virtual routes.
|
||||
//
|
||||
// "reviewing" stays in ProjectRootFolders so case-fold recognition and
|
||||
// future tool registration work, but EnsureCanonicalAncestors skips
|
||||
// MkdirAll for it.
|
||||
var VirtualOnlyCanonicalNames = []string{"reviewing"}
|
||||
|
||||
// IsArchivePartyFolder reports whether dirPath (relative, forward-
|
||||
// slash-separated) names a canonical per-party folder at exactly
|
||||
// depth 4: <project>/archive/<party>/<folder>, where <folder> is one
|
||||
// of mdl/incoming/received/issued (case-insensitive). The party
|
||||
// segment is verbatim.
|
||||
//
|
||||
// Used by listing + dispatch fallbacks so a fresh party that hasn't
|
||||
// yet had files written to each subfolder still lands on a usable
|
||||
// empty browse view rather than 404.
|
||||
func IsArchivePartyFolder(dirPath string) bool {
|
||||
clean := strings.Trim(filepath.ToSlash(dirPath), "/")
|
||||
if clean == "" {
|
||||
return false
|
||||
}
|
||||
parts := strings.Split(clean, "/")
|
||||
if len(parts) != 4 {
|
||||
return false
|
||||
}
|
||||
if !strings.EqualFold(parts[1], "archive") {
|
||||
return false
|
||||
}
|
||||
leaf := strings.ToLower(parts[3])
|
||||
for _, name := range PartyFolders {
|
||||
if leaf == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsArchivePartyMdlDir reports whether dirPath (relative, forward-
|
||||
// slash-separated) names the default-MDL pattern at exactly depth 4:
|
||||
// <project>/archive/<party>/mdl. Match is case-insensitive on the
|
||||
// "archive" and "mdl" segments; the party name is verbatim.
|
||||
//
|
||||
// Used by listing + dispatch fallbacks so a fresh party that hasn't
|
||||
// yet had an MDL written still lands on a usable empty browse / table
|
||||
// view rather than 404. The companion handler helper
|
||||
// isAtArchivePartyMdlDir (in internal/handler/tablehandler.go) takes
|
||||
// absolute paths; this one is the relative-path equivalent for fs.
|
||||
func IsArchivePartyMdlDir(dirPath string) bool {
|
||||
clean := strings.Trim(filepath.ToSlash(dirPath), "/")
|
||||
if clean == "" {
|
||||
return false
|
||||
}
|
||||
parts := strings.Split(clean, "/")
|
||||
if len(parts) != 4 {
|
||||
return false
|
||||
}
|
||||
return strings.EqualFold(parts[1], "archive") &&
|
||||
strings.EqualFold(parts[3], "mdl")
|
||||
}
|
||||
|
||||
// IsProjectRootFolder reports whether dirPath (relative to fsRoot,
|
||||
// forward-slash-separated, no leading slash) names one of the canonical
|
||||
// project-root folders at exactly depth 2: <project>/<canonical>.
|
||||
// Match is case-insensitive against ProjectRootFolders.
|
||||
//
|
||||
// Used by the directory listing endpoint to materialise an empty
|
||||
// listing for canonical folders that don't yet exist on disk, so a
|
||||
// fresh project's nav links never land on 404. The first write under
|
||||
// such a path triggers EnsureCanonicalAncestors which lazily creates
|
||||
// the real on-disk folder + auto-own .zddc.
|
||||
//
|
||||
// Trailing slashes and accidental "./" segments are tolerated. Paths
|
||||
// of any other depth (e.g. project root itself, or deeper subpaths
|
||||
// like working/<email>/) return false — the fallback only applies at
|
||||
// the canonical-folder boundary.
|
||||
func IsProjectRootFolder(dirPath string) bool {
|
||||
clean := strings.Trim(filepath.ToSlash(dirPath), "/")
|
||||
if clean == "" {
|
||||
return false
|
||||
}
|
||||
parts := strings.Split(clean, "/")
|
||||
if len(parts) != 2 {
|
||||
return false
|
||||
}
|
||||
for _, name := range ProjectRootFolders {
|
||||
if strings.EqualFold(parts[1], name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
// Phase 3 retired the hardcoded canonical-folder predicates and their
|
||||
// supporting lists (ProjectRootFolders, PartyFolders, AutoOwnCanonicalNames,
|
||||
// VirtualOnlyCanonicalNames, IsArchivePartyFolder, IsArchivePartyMdlDir,
|
||||
// IsProjectRootFolder). The .zddc cascade is the authority now: see
|
||||
// defaults.zddc.yaml for the canonical convention and lookups.go for
|
||||
// the helpers consumers call (DefaultToolAt, AutoOwnAt, VirtualAt,
|
||||
// IsDeclaredPath, ChildrenDeclaredAt, AvailableToolsAt).
|
||||
|
||||
// WriteAutoOwnZddc serialises a creator-grant .zddc into dir, granting
|
||||
// principalEmail rwcda and recording it in CreatedBy. Used by the file
|
||||
|
|
|
|||
|
|
@ -176,60 +176,7 @@ func TestResolveCanonicalMissingParent(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCanonicalLists(t *testing.T) {
|
||||
hasAll := func(have, want []string) bool {
|
||||
set := map[string]bool{}
|
||||
for _, n := range have {
|
||||
set[n] = true
|
||||
}
|
||||
for _, n := range want {
|
||||
if !set[n] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
if !hasAll(ProjectRootFolders, []string{"archive", "working", "staging", "reviewing"}) {
|
||||
t.Errorf("ProjectRootFolders = %v, missing entries", ProjectRootFolders)
|
||||
}
|
||||
if !hasAll(PartyFolders, []string{"mdl", "incoming", "received", "issued"}) {
|
||||
t.Errorf("PartyFolders = %v, missing entries", PartyFolders)
|
||||
}
|
||||
if !hasAll(AutoOwnCanonicalNames, []string{"working", "staging", "incoming"}) {
|
||||
t.Errorf("AutoOwnCanonicalNames = %v, missing entries", AutoOwnCanonicalNames)
|
||||
}
|
||||
if !hasAll(VirtualOnlyCanonicalNames, []string{"reviewing"}) {
|
||||
t.Errorf("VirtualOnlyCanonicalNames = %v, missing entries", VirtualOnlyCanonicalNames)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsProjectRootFolder(t *testing.T) {
|
||||
cases := map[string]bool{
|
||||
// Canonical positions, lowercase.
|
||||
"Proj/archive": true,
|
||||
"Proj/working": true,
|
||||
"Proj/staging": true,
|
||||
"Proj/reviewing": true,
|
||||
// Case-fold.
|
||||
"Proj/Working": true,
|
||||
"Proj/STAGING": true,
|
||||
"Proj/Reviewing": true,
|
||||
// Trailing slash tolerated (handler trims but be defensive).
|
||||
"Proj/working/": true,
|
||||
// Non-canonical second segment.
|
||||
"Proj/random": false,
|
||||
"Proj/archive2": false,
|
||||
// Wrong depth — root, single segment, or deeper.
|
||||
"": false,
|
||||
"Proj": false,
|
||||
"Proj/working/casey": false,
|
||||
"Proj/archive/ACME": false,
|
||||
"Proj/archive/ACME/issued": false,
|
||||
}
|
||||
for path, want := range cases {
|
||||
got := IsProjectRootFolder(path)
|
||||
if got != want {
|
||||
t.Errorf("IsProjectRootFolder(%q) = %v, want %v", path, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
// TestCanonicalLists and TestIsProjectRootFolder retired in Phase 3 —
|
||||
// the canonical convention is now expressed in defaults.zddc.yaml and
|
||||
// asserted by lookups_test.go (TestDefaultToolAt_FromEmbeddedConvention,
|
||||
// TestIsDeclaredPath_FromEmbeddedConvention, etc.).
|
||||
|
|
|
|||
Loading…
Reference in a new issue