function createValidationResults (rules, model, key, resultsCache, path, config, instance, externalResults, siblingState) {
  // collect the property keys
  const ruleKeys = Object.keys(rules)

  const cachedResult = resultsCache.get(path, rules)
  const $dirty = ref(false)
  // state for the $rewardEarly option
  /** The last invalid state of this property */
  const $lastInvalidState = ref(false)
  /** The last time $commit was called. Used to re-trigger async calls */
  const $lastCommittedOn = ref(0)

  if (cachedResult) {
    // if the rules are the same as before, use the cached results
    if (!cachedResult.$partial) return cachedResult
    // remove old watchers
    cachedResult.$unwatch()
    // use the `$dirty.value`, so we dont save references by accident
    $dirty.value = cachedResult.$dirty.value
  }

  const result = {
    // restore $dirty from cache
    $dirty,
    $path: path,
    $touch: () => { if (!$dirty.value) $dirty.value = true },
    $reset: () => { if ($dirty.value) $dirty.value = false },
    $commit: () => {}
  }

  /**
   * If there are no validation rules, it is most likely
   * a top level state, aka root
   */
  if (!ruleKeys.length) {
    // if there are cached results, we should overwrite them with the new ones
    cachedResult && resultsCache.set(path, rules, result)

    return result
  }

  ruleKeys.forEach(ruleKey => {
    result[ruleKey] = createValidatorResult(
      rules[ruleKey],
      model,
      result.$dirty,
      config,
      instance,
      ruleKey,
      key,
      path,
      siblingState,
      $lastInvalidState,
      $lastCommittedOn
    )
  })

  result.$externalResults = computed(() => {
    if (!externalResults.value) return []
    return [].concat(externalResults.value).map((stringError, index) => ({
      $propertyPath: path,
      $property: key,
      $validator: '$externalResults',
      $uid: `${path}-externalResult-${index}`,
      $message: stringError,
      $params: {},
      $response: null,
      $pending: false
    }))
  })

  result.$invalid = computed(() => {
    const r = ruleKeys.some(ruleKey => unwrap(result[ruleKey].$invalid))
    // cache the last invalid state
    $lastInvalidState.value = r
    return !!result.$externalResults.value.length || r
  })

  result.$pending = computed(() =>
    ruleKeys.some(ruleKey => unwrap(result[ruleKey].$pending))
  )

  result.$error = computed(() =>
    result.$dirty.value ? result.$pending.value || result.$invalid.value : false
  )

  result.$silentErrors = computed(() => ruleKeys
    .filter(ruleKey => unwrap(result[ruleKey].$invalid))
    .map(ruleKey => {
      const res = result[ruleKey]
      return reactive({
        $propertyPath: path,
        $property: key,
        $validator: ruleKey,
        $uid: `${path}-${ruleKey}`,
        $message: res.$message,
        $params: res.$params,
        $response: res.$response,
        $pending: res.$pending
      })
    })
    .concat(result.$externalResults.value)
  )

  result.$errors = computed(() => result.$dirty.value
    ? result.$silentErrors.value
    : []
  )

  result.$unwatch = () => ruleKeys.forEach(ruleKey => {
    result[ruleKey].$unwatch()
  })

  result.$commit = () => {
    $lastInvalidState.value = true
    $lastCommittedOn.value = Date.now()
  }

  resultsCache.set(path, rules, result)

  return result
}
Creates the main Validation Results object for a state tree
Walks the tree's top level branches
@param {Object<NormalizedValidator>} rules - Rules for the current state tree
@param {Object} model - Current state value
@param {String} key - Key for the current state tree
@param {ResultsStorage} [resultsCache] - A cache map of all the validators
@param {String} [path] - the current property path
@param {GlobalConfig} [config] - the config object
@param {VueInstance} instance
@param {ComputedRef<Object>} externalResults
@param {Object} siblingState
@return {ValidationResult | {}}