checkSafeOwnKey = key => {
    if (key === toStringTag) {
      // TODO should we also enforce anything on the contents of the string,
      // such as that it must start with `'Promise'`?
      const tagDesc = getOwnPropertyDescriptor(pr, toStringTag);
      assert(tagDesc !== undefined);
      return (
        (hasOwnPropertyOf(tagDesc, 'value') ||
          CX(
            check,
          )`Own @@toStringTag must be a data property, not an accessor: ${q(tagDesc)}`) &&
        (typeof tagDesc.value === 'string' ||
          CX(
            check,
          )`Own @@toStringTag value must be a string: ${q(tagDesc.value)}`) &&
        (!tagDesc.enumerable ||
          CX(check)`Own @@toStringTag must not be enumerable: ${q(tagDesc)}`)
      );
    }
    const val = pr[key];
    if (val === undefined || typeof val === 'number') {
      return true;
    }
    if (
      typeof val === 'object' &&
      val !== null &&
      isFrozen(val) &&
      getPrototypeOf(val) === Object.prototype
    ) {
      const subKeys = ownKeys(val);
      if (subKeys.length === 0) {
        return true;
      }

      if (
        subKeys.length === 1 &&
        subKeys[0] === 'destroyed' &&
        val.destroyed === false
      ) {
        return true;
      }
    }
    return CX(
      check,
    )`Unexpected Node async_hooks additions to promise: ${pr}.${q(
      String(key),
    )} is ${val}`;
  }
Explicitly tolerate a `toStringTag` symbol-named non-enumerable
data property whose value is a string. Otherwise, tolerate those
symbol-named properties that might be added by NodeJS's async_hooks,
if they obey the expected safety properties.

At the time of this writing, Node's async_hooks contains the
following code, which we can safely tolerate

```js
function destroyTracking(promise, parent) {
  trackPromise(promise, parent);
  const asyncId = promise[async_id_symbol];
  const destroyed = { destroyed: false };
  promise[destroyedSymbol] = destroyed;
  registerDestroyHook(promise, asyncId, destroyed);
}
```

@param {string|symbol} key