feat(kernel-utils): add sheaf programming module#870
Open
Conversation
Contributor
Coverage Report
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
4184513 to
03f6113
Compare
487dd20 to
282a277
Compare
4123110 to
2519237
Compare
0a7b40c to
f4bb458
Compare
f4bb458 to
f59d51b
Compare
342233d to
0de9c94
Compare
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ESCRIPTION Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tion
`getSection({ guard, lift })` now requires an explicit interface guard,
mirroring how `makeExo` always requires one. `getGlobalSection({ lift })`
is the new convenience wrapper that computes the full union guard from
all presheaf sections, analogous to `makeDefaultExo`.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… dispatch Replace string-log side-channels and return-value inference with explicit vi.fn() spy assertions. Each section's handler is a named mock; tests call expect(spy).toHaveBeenCalledWith(...) and .not.toHaveBeenCalled() to verify routing directly rather than inferring it from coincident return values. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rg sections `getGuardAt` was returning `undefined` for positions beyond a section's fixed argument range, even when a `restArgGuard` was present. This caused rest-arg sections to be absent from optional-position unions, producing a false negative: e.g. `M.call().rest(M.string())` would not cover position 0 in the union, so a call `['hello']` would fail the collected guard even though the section accepts it. Fix: fall through to `payload.restArgGuard` after exhausting the optional array, so rest-arg sections contribute to every optional position in the union. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add MetaDataSpec<M> discriminated union (constant | source | callable) so
that sheaf metadata can vary with call arguments rather than being static.
- constant(v) — static value, evaluated once
- source(s) — JS source string compiled via Compartment at sheafify
construction time, called at dispatch time
- callable(fn) — live function called at dispatch time
PresheafSection.metadata changes from M to MetaDataSpec<M> (breaking).
A new EvaluatedSection<M> type carries post-evaluation metadata and is
what Lift receives as its germs array. EvaluatedSection is distinct from
PresheafSection because the "germ" in the sheaf-theoretic sense only
exists after quotienting by the metadata-equivalence relation (the
collapseEquivalent step); EvaluatedSection describes the pre-collapse
stage where the spec has been applied to the invocation args.
getStalk is generalised to <T extends { exo: Section }> so it works over
ResolvedSection (the internal post-resolution type) without a cast.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- getStalk: accept readonly T[] to allow frozen section arrays - evaluatedStalk map: omit metadata when undefined via ifDefined for exactOptionalPropertyTypes (metadata?: M ≠ metadata: M | undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Made-with: Cursor
- evaluateMetadata returns a plain object; missing spec and nullish raw → {}
- reject primitives, arrays, and non-plain objects; hint { value: myValue }
- require EvaluatedSection.metadata; MetaData extends Record<string, unknown>
- simplify metadataKey and decomposeMetadata; drop ifDefined in dispatch
Made-with: Cursor
Replace the one-shot `Lift<M> = (...) => Promise<number>` with an AsyncGenerator coroutine protocol. The lift receives a snapshot of the accumulated error array on each `gen.next(errors)` call, yields candidates one at a time, and can stop early or fall through based on the error history. Add `drive.ts` with `driveLift` to encapsulate the retry loop used by `sheafify.ts`. Add `compose.ts` with `proxyLift`, `withFilter`, `withRanking`, and `fallthrough` as composition primitives. Export all four from `index.ts`. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cover proxyLift, withFilter, withRanking, fallthrough, and composed combinations in compose.test.ts. Includes driveToExhaustion and driveWithSuccessOn test helpers that pass error snapshots (not mutable references) to gen.next, so inner generators can safely store the received arrays. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On exhaustion, throw a new Error with the full errors array as `cause` rather than re-throwing the last error. This preserves the complete failure history for diagnostics. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a Sheaf Module section covering sheafify, metadata kinds, lift authoring, composition helpers, and error handling on exhaustion. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…eaf sections Adds `makeRemoteSection(name, remoteRef, metadata?)` which asynchronously fetches the interface guard from a CapTP remote ref via E()[GET_INTERFACE_GUARD]() and returns a PresheafSection with a local forwarding exo — eliminating the boilerplate of building per-method handlers by hand when wrapping remote caps. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ion methods Adds getDiscoverableSection and getDiscoverableGlobalSection to the Sheaf API so callers can attach a MethodSchema (for __getDescription__) to the caller-facing dispatch section rather than inside individual capability wrappers. Marks getGlobalSection and getDiscoverableGlobalSection as @deprecated — callers should supply an explicit InterfaceGuard via getSection/getDiscoverableSection instead of relying on the auto-computed union. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…terfaces The async interface guard synthesized for a sheaf section must admit implicit exo methods like __getDescription__ that @endo/exo adds to every discoverable exo. Without passable default guards, those methods are rejected at dispatch time, preventing sheafs from being sent across a CapTP connection. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Detaching a method via destructuring or assignment strips the CapTP receiver binding and the remote rejects the call as an "Unexpected receiver". Invoke each method through a fresh E(remote)[method] access so the receiver is preserved on every dispatch. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…holder lift Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n cast Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ncifyMethodGuards to guard.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tocol violation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…se only Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lic exports Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ve.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
"Metadata" is one compound word; the mid-word capital was inconsistent with the surrounding identifiers and prose docs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The alias added a second public name for PresheafSection<M>[] with no external consumers. Callers write the array type directly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The guard is passed dynamically at call time so TypeScript cannot propagate the method signatures through Sheaf<M>. The comment prevents future contributors from chasing a phantom improvement. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ndler failure Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- LIFT.md: fix exhaustion description to match actual error shape - README.md: remove stale "registry" and "tracks" claims post-revocation-removal - types.ts: remove "revocable" from Sheaf method docs; clarify when to use global section variants vs explicit-guard variants - USAGE.md: use makeSection (public API) in single-provider example; clarify proxyLift vs yield* for lift composition Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
0de9c94 to
4f47c89
Compare
…dataKey conflation bugs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
gen.next(errors) was passing the same live mutable array reference on every resume. A lift that stores the received value from one yield and inspects it after a later yield would see mutations from subsequent failures. Pass [...errors] snapshots so each yield receives an independent copy. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… conflation
JSON.stringify maps undefined, NaN, Infinity, and -Infinity all to null,
so sections with e.g. { cost: Infinity } and { cost: null } produced
identical keys and were incorrectly collapsed into one germ. Replace the
plain JSON.stringify(entries) with encodeMetadataEntry, which includes a
typeof tag in each tuple so all of these distinct values produce distinct
keys. BigInt metadata values no longer throw at serialization time either.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sheaf is a large, self-contained subsystem. Keeping it under its own subpath import reduces coupling on consumers who don't need it, and keeps the main index focused on general utilities. - Add @metamask/kernel-utils/sheaf entry point (src/sheaf/index.ts) - Remove sheaf re-exports from the main index - Add ./sheaf export to package.json alongside the other subpaths - Remove sheaf overview from README (belongs in sheaf/README.md) - Update CHANGELOG: use subpath import, drop internal exports (collectSheafGuard, getStalk, guardCoversPoint), add makeSection and noopLift, fix MetadataSpec capitalisation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…decomposeMetadata Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
=== fails for NaN (NaN !== NaN), so a NaN value shared by all germs was never promoted to a constraint — it remained in each germ's distinguishing metadata instead. Object.is correctly treats NaN === NaN and is consistent with the type-tagged encoding already used in collapseEquivalent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 2d84d4a. Configure here.
…aKey Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
JSON.stringify(-0) produces "0", so -0 and +0 were serialised to the same metadataKey and incorrectly collapsed into one germ by collapseEquivalent. Object.is(0, -0) is false, so decomposeMetadata already treated them as distinct — making the two functions inconsistent. Add -0 as an explicit special case alongside NaN, +Infinity, -Infinity. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Introduce operational presheaf + sheafify for guard-based dispatch:
Note
Medium Risk
Introduces a new dispatch/routing subsystem (
sheafify, lift coroutines, guard unioning, remote forwarding) that will be consumed by other packages, so correctness of selection/guard logic and metadata equivalence is important though changes are largely additive.Overview
Adds a new
@metamask/kernel-utils/sheafmodule that builds a capability “router” (sheafify) over multiple exo sections, selecting an implementation at call time by filtering sections via interface guards, evaluating optional per-invocation metadata, collapsing metadata-equivalent candidates, and invoking a caller-suppliedLiftcoroutine to rank/retry candidates.Includes utilities for authoring sections and policies (
makeSection,constant/callable/sourcemetadata specs, and lift combinators likenoopLift,withFilter,withRanking,fallthrough,proxyLift), plusmakeRemoteSectionto wrap CapTP remote references and forward calls viaE().Updates
package.jsonexports and dependencies (adds@endo/eventual-send) and adds extensive unit/e2e tests and documentation for guard collection, stalk filtering, metadata evaluation, and lift retry semantics.Reviewed by Cursor Bugbot for commit c43872e. Bugbot is set up for automated code reviews on this repo. Configure here.