R-839: the deploy API refuses an HDD_PATH that is a folder inside a registered drive
Diagnosed: 9202's paperless-ngx app.yaml named <drive>/userdata/paperless-ngx, written by the 2026-09-22 drill harness through POST /api/stacks/<n>/deploy, which accepted it (RefuseAsAppNamespace matches by prefix). The boot sweep then asked a folder whether it was a mountpoint and HELD the app. The deploy page offers drive roots only, so a household could not reach this; every other caller is now refused with a sentence naming the drive (settings.DriveOfSubPath). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -2119,6 +2119,29 @@ func (s *Settings) RefuseAsAppNamespace(path string) (bool, string) {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// DriveOfSubPath reports the registered storage path that `path` lies strictly INSIDE (R-839). An app's
|
||||
// HDD_PATH names a drive root — the namespace root, the userdata dir and the backups tree are all
|
||||
// derived from it — so a value one or more segments below a registered drive is never valid: the boot
|
||||
// sweep's mountpoint check then asks about a folder rather than the drive, and HOLDS the app after
|
||||
// every restart (measured on 9202: `<drive>/userdata/paperless-ngx`, written by a drill harness through
|
||||
// the deploy API, which accepted it because RefuseAsAppNamespace matches by prefix). Exact matches and
|
||||
// unregistered paths return ok=false. Pinned by TestR839_DeployRefusesASubPathOfADrive.
|
||||
func (s *Settings) DriveOfSubPath(path string) (string, bool) {
|
||||
path = strings.TrimRight(strings.TrimSpace(path), "/")
|
||||
if path == "" || s == nil {
|
||||
return "", false
|
||||
}
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
for _, sp := range s.StoragePaths {
|
||||
root := strings.TrimRight(sp.Path, "/")
|
||||
if root != "" && strings.HasPrefix(path, root+"/") {
|
||||
return sp.Path, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// Refusal reasons for RefuseAsAppNamespace. Hungarian, adult tone, no emoji — these reach the customer
|
||||
// through the deploy/migrate error surfaces. They name the storage class and what to do instead, never
|
||||
// an internal path or field name.
|
||||
|
||||
Reference in New Issue
Block a user