R-203: the app and its backup look in the same directory — one resolver, every caller
gates / gates (push) Successful in 9s
gates / gates (push) Successful in 9s
appbackup's path helpers take a NAMESPACE ROOT. Five call sites passed a bare DRIVE path.
On an enrolled drive the two coincide, so nothing showed; on the system-data fallback they
differ by exactly the felhom-data segment, and the app then bound a directory the off-site
capture set never looked at -- while the run reported ok. Measured live on demo-hp: the app
wrote to /mnt/sys_drive/userdata/media/books, the capture set looked for
/mnt/sys_drive/felhom-data/userdata/media/books.
THE RULE NOW HAS ONE EXPRESSION. appbackup.NamespaceRootFor / IsEnrolledDrive encode the
drive-kind comparison; backup.Manager.namespaceRoot and stacks.Manager.inGuest delegate to
it. There were already TWO copies and they differed -- the backup package's compared without
filepath.Clean, the stacks package's with it, so a trailing slash from config would have
flipped the mode in one and not the other.
Sites routed through it:
- stacks/deploy.go withPathVars -> ${USERDATA_PATH} (the live defect)
- appexport/fabplan.go + export.go (via a new provider method)
- web/handlers.go FileBrowser mounts (latent: the system drive is
deliberately never a registered StoragePath, so this is the identity today)
ComputeFabBuckets now receives the namespace root, which is what ComputeCaptureSet has always
received -- so the export's classified paths and the backup's capture set describe the same
directories by construction instead of by coincidence.
Tests are table-driven over BOTH drive kinds, because this survived by being invisible on the
kind that already worked. Red-proofs observed: restoring the bare-path call fails the
system-drive row with the two paths differing by /felhom-data; inverting the drive-kind
comparison fails every enrolled row.
This commit is contained in:
@@ -32,6 +32,35 @@ func NamespaceRoot(drivePath string, inGuestDrive bool) string {
|
||||
return filepath.Join(drivePath, FelhomDataDir)
|
||||
}
|
||||
|
||||
// IsEnrolledDrive reports whether a drive path is an ENROLLED user-data drive (Model A: its in-guest
|
||||
// mount already IS the namespace root) rather than the system-data fallback. It is the ONE comparison
|
||||
// that decides which NamespaceRoot mode applies, and it lives here so no package re-derives it.
|
||||
//
|
||||
// Both sides are Clean'd: `/mnt/sys_drive/` and `/mnt/sys_drive` are the same drive, and a trailing
|
||||
// slash arriving from config must not silently flip the mode.
|
||||
func IsEnrolledDrive(drivePath, systemDataPath string) bool {
|
||||
return filepath.Clean(drivePath) != filepath.Clean(systemDataPath)
|
||||
}
|
||||
|
||||
// NamespaceRootFor is the resolver every caller should use when it holds a bare DRIVE path and the
|
||||
// system-data path — i.e. everywhere outside the backup package, which already had this rule.
|
||||
//
|
||||
// R-203: FIVE call sites passed a bare drive path straight to UserdataDir (and its siblings), which
|
||||
// take a NAMESPACE ROOT. On an enrolled drive the two coincide, so nothing showed; on the system-data
|
||||
// fallback they differ by exactly the felhom-data segment, and the app then bound a directory the
|
||||
// backup never looked at. The run still reported ok. Measured live on demo-hp 2026-08-04:
|
||||
// the app wrote to /mnt/sys_drive/userdata/media/books while the off-site capture set looked for
|
||||
// /mnt/sys_drive/felhom-data/userdata/media/books.
|
||||
//
|
||||
// THE CONTRACT, restated because four callers got it wrong and a fifth will: UserdataDir,
|
||||
// PrimaryBackupPath, RecoveryUnitPath and AppDataDir all take a NAMESPACE ROOT. If you are holding
|
||||
// something that came out of HDD_PATH or a StoragePath, it is a DRIVE path — put it through here
|
||||
// first. `UserdataDir(bareDrivePath)` still compiles and is still wrong; TestNoBareDrivePathToUserdataDir
|
||||
// is the guard that keeps the count from growing.
|
||||
func NamespaceRootFor(drivePath, systemDataPath string) string {
|
||||
return NamespaceRoot(drivePath, IsEnrolledDrive(drivePath, systemDataPath))
|
||||
}
|
||||
|
||||
// PrimaryBackupPath returns the root primary backup directory under a felhom-data namespace root.
|
||||
func PrimaryBackupPath(nsRoot string) string {
|
||||
return filepath.Join(nsRoot, "backups", "primary")
|
||||
|
||||
Reference in New Issue
Block a user