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:
ZDDC 2026-05-11 16:01:43 -05:00
parent 5e393cbeaf
commit 6310afa922
5 changed files with 12 additions and 211 deletions

View file

@ -260,24 +260,6 @@ func isNotExistError(err error) bool {
return err != nil && strings.Contains(err.Error(), "no such file or directory") 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 // ServeTable serves the static tables.html bytes for a recognized
// request. ACL gate is the read action at the request directory; on // request. ACL gate is the read action at the request directory; on
// allow, the embedded HTML is written verbatim. The client takes over // allow, the embedded HTML is written verbatim. The client takes over

View file

@ -1300,7 +1300,7 @@ body.help-open .app-header {
</svg> </svg>
<div class="header-title-group"> <div class="header-title-group">
<span class="app-header__title" id="table-title">ZDDC Table</span> <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> </div>
<div class="header-right"> <div class="header-right">

View file

@ -35,9 +35,6 @@ func DefaultToolAt(fsRoot, dirPath string) string {
// propagate to descendants (creating working/alice/notes/sub/ does // propagate to descendants (creating working/alice/notes/sub/ does
// not auto-own sub/; only the explicitly-declared per-user home is // not auto-own sub/; only the explicitly-declared per-user home is
// auto-owned). // auto-owned).
//
// Replaces AutoOwnCanonicalNames once the file API's mkdir hook is
// migrated.
func AutoOwnAt(fsRoot, dirPath string) bool { func AutoOwnAt(fsRoot, dirPath string) bool {
chain, err := EffectivePolicy(fsRoot, dirPath) chain, err := EffectivePolicy(fsRoot, dirPath)
if err != nil { if err != nil {
@ -75,8 +72,6 @@ func AutoOwnFencedAt(fsRoot, dirPath string) bool {
// purely virtual (never materialise on disk). Leaf-only: the virtual // purely virtual (never materialise on disk). Leaf-only: the virtual
// property describes a particular path, not a subtree. A child of a // property describes a particular path, not a subtree. A child of a
// virtual directory is not automatically virtual itself. // virtual directory is not automatically virtual itself.
//
// Replaces VirtualOnlyCanonicalNames once consumers are migrated.
func VirtualAt(fsRoot, dirPath string) bool { func VirtualAt(fsRoot, dirPath string) bool {
chain, err := EffectivePolicy(fsRoot, dirPath) chain, err := EffectivePolicy(fsRoot, dirPath)
if err != nil { 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 // even if the directory doesn't exist on disk. Used by listing
// fallbacks to decide whether a missing directory should return an // fallbacks to decide whether a missing directory should return an
// empty listing (treat as virtual) vs 404 (truly unknown). // empty listing (treat as virtual) vs 404 (truly unknown).
//
// Replaces IsProjectRootFolder + IsArchivePartyFolder once
// consumers are migrated.
func IsDeclaredPath(fsRoot, dirPath string) bool { func IsDeclaredPath(fsRoot, dirPath string) bool {
chain, err := EffectivePolicy(fsRoot, dirPath) chain, err := EffectivePolicy(fsRoot, dirPath)
if err != nil { if err != nil {

View file

@ -6,133 +6,13 @@ import (
"strings" "strings"
) )
// ProjectRootFolders are the canonical lowercase folder names that may // Phase 3 retired the hardcoded canonical-folder predicates and their
// appear directly under a project root. The server resolves them // supporting lists (ProjectRootFolders, PartyFolders, AutoOwnCanonicalNames,
// case-insensitively on disk: a manually-created Working/ is reused // VirtualOnlyCanonicalNames, IsArchivePartyFolder, IsArchivePartyMdlDir,
// rather than shadowed by a new working/. // IsProjectRootFolder). The .zddc cascade is the authority now: see
// // defaults.zddc.yaml for the canonical convention and lookups.go for
// - "archive" — formal record of issued/received transmittals, // the helpers consumers call (DefaultToolAt, AutoOwnAt, VirtualAt,
// organised by counterparty (and ourselves) // IsDeclaredPath, ChildrenDeclaredAt, AvailableToolsAt).
// - "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
}
// WriteAutoOwnZddc serialises a creator-grant .zddc into dir, granting // WriteAutoOwnZddc serialises a creator-grant .zddc into dir, granting
// principalEmail rwcda and recording it in CreatedBy. Used by the file // principalEmail rwcda and recording it in CreatedBy. Used by the file

View file

@ -176,60 +176,7 @@ func TestResolveCanonicalMissingParent(t *testing.T) {
} }
} }
func TestCanonicalLists(t *testing.T) { // TestCanonicalLists and TestIsProjectRootFolder retired in Phase 3 —
hasAll := func(have, want []string) bool { // the canonical convention is now expressed in defaults.zddc.yaml and
set := map[string]bool{} // asserted by lookups_test.go (TestDefaultToolAt_FromEmbeddedConvention,
for _, n := range have { // TestIsDeclaredPath_FromEmbeddedConvention, etc.).
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)
}
}
}