package hostclaim import ( "encoding/json" "strconv" "strings" ) type operationLossGate struct { Box string `json:"box"` RevokeExecution bool `json:"revoke_execution"` } // OperationLossID selects only a declaration journalled under this operation. // Old claim-level LossRefs alone do not turn a later move into a loss recovery. func OperationLossID(c *Claim) (string, error) { if c == nil { return "", ErrLossRecord } id := "" for key := range c.Op.Journal { if !strings.HasPrefix(key, "loss.") { continue } next := strings.TrimPrefix(key, "loss.") if c.Op.ID == "" || !safeLossID(next) || !lossRefPresent(c, next) { return "", ErrLossRecord } var gate operationLossGate if raw := c.Op.Journal["loss_gate."+next]; raw != "" { if json.Unmarshal([]byte(raw), &gate) != nil || gate.Box == "" { return "", ErrLossRecord } } else { var legacy struct { Kind, Target string RevokeExecution bool `json:"revoke_execution"` } if json.Unmarshal([]byte(c.Op.Journal[key]), &legacy) != nil { return "", ErrLossRecord } gate.Box, gate.RevokeExecution = legacy.Target, legacy.RevokeExecution if gate.Box == "" && legacy.Kind == "move-target-lost" { gate.Box = c.Op.Intent["target"] } if gate.Box == "" && !gate.RevokeExecution { return "", ErrLossRecord } } if gate.Box != c.Op.Intent["target"] && !gate.RevokeExecution { continue } if id != "" { return "", ErrLossRecord } id = next } return id, nil } // OperationLossConsumed survives admission-history trimming and process restart. // Only the verified admission CAS records this operation-local marker. func OperationLossConsumed(c *Claim, id string) bool { if c == nil || c.Holder == nil || id == "" { return false } n, err := strconv.ParseUint(c.Op.Journal["loss_admitted."+id], 10, 64) return err == nil && n > 0 && n <= c.Holder.Session }