package moves import ( "fmt" "strconv" "strings" "github.com/carlosframework/platform/internal/hostclaim" "github.com/superfly/ltx" ) // ReplicaSource is where a move's source copy reads from (ยง7.3, round-3 // finding 5): the claim holder's location and generation, never the // legacy HibernatePrefix guess. keep admits, relative to prefix, only the // generation's own LTX files, `/-.ltx` with a level from // 0000 to 0009, so the staging lane holds the generation's files flat // under replica// and nothing else: no other generation (g-*/), no // child incarnation, no generation.json, lock.json or closed/. // // The holder's location never carries a trailing slash (Claim.Validate // accepts only instances/ or instances//), so the // separator is added here, as genselect.requestLocation does (round-4 // finding 4). A generation of "" or hostclaim.RootGeneration is the root // layout: the level directories sit directly under the location. // // The generation prefix rule has twins: hostbox.GenerationPrefix (which // refuses the root generation and a holder with no session, so a source // copy cannot use it) and genselect's unexported prefix and coordinates. // The three change together. func ReplicaSource(h hostclaim.Holder) (prefix string, keep func(rel string) bool) { base := strings.TrimSuffix(h.Location, "/") + "/" prefix = base if g := h.Generation; g != "" && g != hostclaim.RootGeneration { prefix = base + "g-" + g + "/" } return prefix, levelFile } // levelFile is ReplicaSource's keep: exactly two path parts, a level // directory of four lowercase hex digits from 0000 to 0009, and an LTX // file name in its canonical form with 0 < min <= max. func levelFile(rel string) bool { parts := strings.Split(rel, "/") if len(parts) != 2 || len(parts[0]) != 4 { return false } level, err := strconv.ParseUint(parts[0], 16, 16) if err != nil || level > 9 || fmt.Sprintf("%04x", level) != parts[0] { return false } min, max, err := ltx.ParseFilename(parts[1]) return err == nil && min != 0 && min <= max && ltx.FormatFilename(min, max) == parts[1] }