ZDDC/zddc/internal/zddc/special_test.go
ZDDC 918f330a6f feat(zddc): WORM as a cascade key (worm:), retiring hardcoded path predicates
WORM (write-once-read-many) is no longer a special folder type keyed
off the literal names "received"/"issued". It's a cascade key —
`worm:` on any directory's .zddc — with the ACL-shaped semantics the
user described.

Schema:
  worm:
    "doc-control@example.com": cr   # email-glob or @role:name → verbs ⊆ {r, c}
  # an empty map ({}) is a WORM zone with no create-capable principals

Effect inside a WORM zone (any cascade level declares worm:), applied
AFTER the normal cascade ACL and BEFORE the admin escape hatch:
  - w / d / a stripped for everyone
  - c survives only via the worm: map
  - r survives via the normal ACL OR the worm: map (so a document
    controller who isn't in the project ACL still gets read+create)
  - worm: grants UNION across the cascade — deeper .zddc can name
    more controllers
  - admins (root / subtree) bypass entirely — handler does the
    IsAdmin check before the policy evaluator

defaults.zddc.yaml: archive/<party>/received and archive/<party>/issued
carry `worm: {}` (WORM zone, no controllers — the deployment names
its document controller by adding a deeper .zddc with
`worm: {<principal>: cr}`). The canonical convention is unchanged;
the difference is an operator can now mark any directory WORM, or
rename received/issued, without a code change.

Removed (hardcoded path predicates, superseded by the cascade walk):
  zddc.IsWormPath
  zddc.WormFolderLevelIndex
  zddc.splitPathSegments  (only IsWormPath used it)
Kept: zddc.WormMask (generic verb-set primitive), zddc.VerbsRC.

New:
  zddc.WormZoneGrant(chain, email, mode) → (verbs, inWormZone)
    Walks the chain for worm: declarations; unions the principal's
    grants masked to {r, c}.
  policy.InternalDecider.Allow: WORM block rewritten to consult
    WormZoneGrant instead of IsWormPath/WormFolderLevelIndex.
  ValidateFile: worm: keys validated as email-glob (or @role:name);
    values validated as verb strings ⊆ {r, c}.

Tests:
  - new worm_test.go covers the embedded convention, operator-granted
    controller, w/d masking, cross-cascade union.
  - special_test.go's TestIsWormPath / TestWormFolderLevelIndex
    retired; TestWormMaskStripsWDA kept.
  - fileapi_test.go's WORM tests updated: the doc-controller grant is
    now `worm: { _doc_controller: cr }` at issued/.zddc, not
    `acl.permissions: { _doc_controller: cr }`.
  - federal-parity and admin-bypass tests unchanged — the WORM mask
    still strips w/d/a and admins still bypass.

All Go tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 08:29:11 -05:00

123 lines
4 KiB
Go

package zddc
import (
"os"
"path/filepath"
"testing"
)
func TestIsAutoOwnPath(t *testing.T) {
root := "/srv/zddc"
cases := map[string]bool{
// Project-root canonical positions.
"/srv/zddc/Project/working": true,
"/srv/zddc/Project/staging": true,
"/srv/zddc/Project/Working": true, // case-fold
"/srv/zddc/Project/STAGING": true, // case-fold
"/srv/zddc/Project/archive": false,
"/srv/zddc/Project/reviewing": false,
"/srv/zddc/Project/random": false,
// Per-party position.
"/srv/zddc/Project/archive/ACME/incoming": true,
"/srv/zddc/Project/archive/ACME/Incoming": true, // case-fold
"/srv/zddc/Project/Archive/ACME/incoming": true, // case-fold archive
"/srv/zddc/Project/archive/ACME/received": false,
"/srv/zddc/Project/archive/ACME/issued": false,
"/srv/zddc/Project/archive/ACME/mdl": false,
// Wrong depth — incoming inside something other than archive/<party>/.
"/srv/zddc/Project/working/incoming": false,
"/srv/zddc/Project/random/sub/incoming": false,
"/srv/zddc/Project/incoming": false, // depth 1 with incoming
"/srv/zddc/Project/archive/incoming": false, // depth 2
"/srv/zddc/Project/archive/ACME/incoming/sub": false, // child of incoming, not incoming itself
// Outside root.
"/elsewhere/working": false,
// Root itself or one above.
"/srv/zddc": false,
"/srv/zddc/Project": false,
}
for in, want := range cases {
if got := IsAutoOwnPath(in, root); got != want {
t.Errorf("IsAutoOwnPath(%q, %q) = %v, want %v", in, root, got, want)
}
}
}
// TestIsWormPath / TestWormFolderLevelIndex retired — WORM zones are
// declared via the `worm:` key now (see worm.go's WormZoneGrant,
// exercised by worm_test.go). The convention (received/issued are
// WORM) lives in defaults.zddc.yaml and is asserted via the cascade
// lookup, not a path-segment predicate.
func TestWormMaskStripsWDA(t *testing.T) {
rwcda, _ := ParseVerbSet("rwcda")
masked := WormMask(rwcda)
if got := masked.String(); got != "rc" {
t.Errorf("WormMask(rwcda) = %q, want rc", got)
}
rw, _ := ParseVerbSet("rw")
if got := WormMask(rw).String(); got != "r" {
t.Errorf("WormMask(rw) = %q, want r", got)
}
cd, _ := ParseVerbSet("cd")
if got := WormMask(cd).String(); got != "c" {
t.Errorf("WormMask(cd) = %q, want c", got)
}
if got := WormMask(0).String(); got != "" {
t.Errorf("WormMask(0) = %q, want empty", got)
}
}
func TestResolveCanonicalCaseFold(t *testing.T) {
dir := t.TempDir()
if err := os.MkdirAll(filepath.Join(dir, "Working"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(filepath.Join(dir, "ARCHIVE"), 0o755); err != nil {
t.Fatal(err)
}
// A regular file with a canonical name must NOT be returned (we only resolve directories).
if err := os.WriteFile(filepath.Join(dir, "staging"), []byte{}, 0o644); err != nil {
t.Fatal(err)
}
cases := map[string]string{
"working": "Working", // PascalCase wins because it exists on disk
"WORKING": "Working",
"Working": "Working",
"archive": "ARCHIVE",
"reviewing": "", // not present
"staging": "", // present as a file, not a directory — must skip
}
for logical, want := range cases {
got, err := ResolveCanonical(dir, logical)
if err != nil {
t.Errorf("ResolveCanonical(%q): %v", logical, err)
continue
}
if got != want {
t.Errorf("ResolveCanonical(%q) = %q, want %q", logical, got, want)
}
}
}
func TestResolveCanonicalMissingParent(t *testing.T) {
got, err := ResolveCanonical(filepath.Join(t.TempDir(), "does-not-exist"), "working")
if err != nil {
t.Errorf("expected nil error for missing parent, got %v", err)
}
if got != "" {
t.Errorf("expected empty result for missing parent, got %q", got)
}
}
// 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.).