Skip to content

sheaves: guardCoversPoint, getGuardAt, and collectSheafGuard read rest guards per argument #1150

Description

@ci-belphegor

Priority: low. This fails closed. A call that gets through still reaches a section's own exo, which enforces its guard correctly, so no authority escapes. But the sheaf refuses calls its sections admit, advertises calls none of them admit, and chooses sections from a wrong reading of their guards.

The rule

Exo checks a call by matching the whole argument array against M.splitArray(argGuards, optionalArgGuards, restArgGuard) (buildMatchConfig in @endo/exo). The rest guard therefore matches the array of trailing arguments, not each argument. "Any number of trailing strings" is .rest(M.arrayOf(M.string())). .rest(M.string()) refuses every call, even one with no trailing arguments, because [] is not a string.

Where sheaves reads it per argument

guardCoversPoint (packages/sheaves/src/match.ts) checks each trailing argument against the rest guard:

args.slice(maxFixedArgs).every((arg) => matches(arg, restArgGuard))
  • For a section guarded as log: M.call(M.string()).rest(M.arrayOf(M.string())), exo admits log('a', 'b'), but guardCoversPoint leaves the section out.
  • For a section guarded as .rest(M.string()), guardCoversPoint selects it for log('a', 'b'), and its exo rejects the call, as it rejects every call.

getGuardAt (packages/kernel-utils/src/guard-algebra.ts) returns the rest guard as the guard for a single position past the fixed arity.

collectSheafGuard (packages/sheaves/src/guard.ts) builds per-position unions from getGuardAt. With section A guarded as (string).rest(M.arrayOf(M.number())) and section B guarded as (string, string), position 1 becomes an optional M.or(M.arrayOf(M.number()), M.string()). The sheaf's guard then:

  • admits ('s', [1, 2]), which neither section admits;
  • refuses ('s', 1, 2), which A admits.

Separately from the rest misreading, ORing position by position admits cross-combinations across sections, such as A's argument 0 with B's argument 1. join had the same flaw, fixed in #1134 by rendering one M.splitArray per row.

Suggested fix

  • Add a kernel-utils helper that checks a call against a method guard as exo does: match harden(args) against M.splitArray(argGuards, optionals, restArgGuard), and without a rest guard also cap the argument count. Use it in guardCoversPoint.
  • Have collectSheafGuard render a method whose sections differ as M.callWhen().rest(M.or(M.splitArray(...), …)), one per section, which is exactly their union, as join does in feat(kernel-utils): attenuation by narrowing #1134. Then getGuardAt needs no rest branch: it can return undefined past the fixed arity, or be removed. It is unreleased.
  • In guardCoversPoint, use Object.hasOwn rather than method in methodGuards, which matches inherited names such as toString, and admit an explicit undefined at an optional position, as exo does.
  • Rewrite the tests that assert matches(value, restArgGuard) against single values (sheaves/src/guard.test.ts, kernel-utils/src/guard-algebra.test.ts) as call-level checks.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions