ZDDC/zddc/internal/apps/availability.go
ZDDC 1e0e403f1e feat(zddc): retire defaults.zddc.yaml; .zddc.zip is the policy carrier (phase 6)
Completes the migration. The embedded per-depth tree (internal/zddc/defaults/)
is now the sole source of the shipped baseline; defaults.zddc.yaml is deleted.

  - EmbeddedDefaults() assembles the tree (no yaml). show-defaults now emits a
    .zddc.zip (per-depth, "*" wildcard members) via EmbeddedDefaultsZip() —
    operators redirect it to <ROOT>/.zddc.zip (or any directory) and edit/add/
    delete individual members.
  - Dropped EmbeddedDefaultsBytes; reworked the dumpable test to validate the
    emitted zip; removed the now-redundant tree-vs-yaml oracle (the Layer-2
    matrix is the ongoing behavioral guarantee, and it stays green).
  - Swept stale "defaults.zddc.yaml" comment references to the embedded tree.
  - GRAMMAR.md §1/§6 updated: .zddc.zip is a policy bundle mountable at ANY
    directory (subtree mount; inherit:false + acl.inherit:false = island); the
    shipped baseline is the embedded bundle at the root.

Net of the 6-phase migration: policy is per-depth .zddc files in a .zddc.zip
that an operator can drop at any level to override the cascade; the engine
(Assemble + the unchanged walker) enforces it. Full Go suite + matrix green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 11:35:21 -05:00

100 lines
4.3 KiB
Go

package apps
import (
"path/filepath"
"strings"
"codeberg.org/VARASYS/ZDDC/zddc/internal/zddc"
)
// AppAvailableAt reports whether app's virtual HTML can be served at
// requestDir. Delegates to the .zddc cascade's available_tools union
// (zddc.IsToolAvailableAt). The convention previously hardcoded here
// now lives in internal/zddc/defaults/ and is overridable per-directory
// by operators.
//
// Operators can always drop a real <name>.html file at any path to
// override — that path is served by the static handler regardless of
// this function's result. AppAvailableAt is consulted only when no
// real file exists.
//
// Landing is a special case: the cascade declares it available
// universally (since available_tools concat-merges from the root
// baseline), but the dispatcher only auto-serves landing at the
// deployment root. We enforce that here too so callers don't trip
// over project-deep landing requests.
func AppAvailableAt(root, requestDir, app string) bool {
root = filepath.Clean(root)
requestDir = filepath.Clean(requestDir)
if app == "landing" {
return requestDir == root
}
return zddc.IsToolAvailableAt(root, requestDir, app)
}
// DefaultAppAt returns the canonical default tool name for requestDir,
// or "" if no specific tool fits. Used by the dispatcher to decide
// which app to serve at a directory URL with no trailing slash —
// trailing-slash URLs serve the browse app for any directory.
//
// Rules (case-insensitive on canonical folder names), under the
// May 2026 reshape:
//
// - <project>/archive/<party>/{mdl,rsk}/... → "tables"
// - <project>/archive/<party>/staging/... → "transmittal"
// - <project>/archive/<party>/{working,reviewing}/...
// → "browse" (hosts the
// markdown editor plugin)
// - <project>/archive/<party>/incoming/... → "classifier"
// - <project>/archive/<party>/{received,issued}/...
// → "archive"
// - <project>/archive/ → "archive"
// - <project>/{ssr,mdl,rsk} → "tables" (project-
// level rollup virtuals
// with synthesized
// $party column)
// - <project>/{working,staging,reviewing} → "browse" (project-
// level folder-nav
// virtuals — per-party
// URLs 302 to the
// canonical archive/
// <party>/<slot>/)
// - any other directory → "" (no default)
//
// The {mdl,rsk} rule wins over the broader archive rule because the
// table editor is a more specific surface for browsing planned
// deliverables than the archive index. Note: the dir at
// archive/<party>/mdl/ itself IS the table — its table.yaml +
// form.yaml + row YAMLs all live there together (self-contained
// directory).
//
// requestDir and root are absolute filesystem paths; requestDir must
// be under root (otherwise "" is returned).
//
// Phase 3b: delegates to zddc.DefaultToolAt, which resolves the
// answer from the .zddc cascade (operator on-disk + embedded
// defaults). The convention previously hardcoded in the switch
// statement below now lives in zddc/internal/zddc/defaults/
// and is overridable per-directory by operators.
//
// Project root itself (depth-1) still returns "" — the cascade
// doesn't declare a default tool for it (landing is handled by the
// dispatcher's own depth-1 check).
func DefaultAppAt(root, requestDir string) string {
root = filepath.Clean(root)
requestDir = filepath.Clean(requestDir)
if requestDir == root {
return ""
}
rel, err := filepath.Rel(root, requestDir)
if err != nil || strings.HasPrefix(rel, "..") {
return ""
}
parts := strings.Split(rel, string(filepath.Separator))
if len(parts) < 2 {
// Project root itself — no default tool from the cascade.
// (Landing is handled separately by the dispatcher.)
return ""
}
return zddc.DefaultToolAt(root, requestDir)
}