Skip to content

feat(kernel-utils): add sheaf programming module#870

Open
grypez wants to merge 54 commits intomainfrom
grypez/bringing-in-the-sheaves
Open

feat(kernel-utils): add sheaf programming module#870
grypez wants to merge 54 commits intomainfrom
grypez/bringing-in-the-sheaves

Conversation

@grypez
Copy link
Copy Markdown
Contributor

@grypez grypez commented Mar 4, 2026

Introduce operational presheaf + sheafify for guard-based dispatch:

  • Section/guard types, presheaf construction, stalk filtering
  • Late decider (lift) selects winner when multiple sections match
  • Modular sheaf/ directory with single-concern files and e2e tests

Note

Medium Risk
Mostly additive new package, but it introduces new dispatch/routing logic (guard matching, metadata evaluation, policy-driven retries) that will affect callers adopting it and could surface subtle runtime edge cases.

Overview
Adds a new @metamask/sheaves package that implements guard-based capability routing via sheafify, producing dispatch sections that select among matching providers using evaluated metadata and a caller-supplied async-generator Policy (including retry with accumulated errors and constraint/option metadata decomposition).

Includes helpers for metadata specs (constant/callable/source with optional compartment compilation), policy composition utilities (noopPolicy, withFilter, withRanking, fallthrough, proxyPolicy), remote-provider wrapping via makeRemoteSection, plus extensive unit/e2e tests and documentation. Wires the package into the monorepo build/test setup (new tsconfig refs, vitest config, lockfile entry) and adds standard package metadata/licensing/changelog.

Reviewed by Cursor Bugbot for commit ba12dbe. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 4, 2026

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 71.96%
⬆️ +0.53%
8503 / 11816
🔵 Statements 71.79%
⬆️ +0.53%
8644 / 12040
🔵 Functions 72.83%
⬆️ +0.58%
2054 / 2820
🔵 Branches 65.58%
⬆️ +0.49%
3434 / 5236
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/sheaves/src/compose.ts 94.44% 100% 90.9% 94.44% 16
packages/sheaves/src/guard.ts 98.59% 96.15% 100% 98.52% 109
packages/sheaves/src/index.ts 100% 100% 100% 100%
packages/sheaves/src/metadata.ts 100% 100% 100% 100%
packages/sheaves/src/remote.ts 100% 100% 100% 100%
packages/sheaves/src/section.ts 100% 100% 100% 100%
packages/sheaves/src/sheafify.ts 93.69% 82.92% 100% 93.63% 39, 42, 52, 120, 163, 247, 277-282
packages/sheaves/src/stalk.ts 88.23% 86.66% 100% 88.23% 31, 69
packages/sheaves/src/types.ts 100% 100% 100% 100%
Generated in workflow #4410 for commit ba12dbe by the Vitest Coverage Report Action

@grypez grypez force-pushed the grypez/schema-dunder branch from 4184513 to 03f6113 Compare March 9, 2026 19:07
@grypez grypez force-pushed the grypez/bringing-in-the-sheaves branch from 487dd20 to 282a277 Compare March 9, 2026 19:08
@grypez grypez changed the title feat(kernel-exo): add sheaf programming module feat(kernel-utils): add sheaf programming module Mar 9, 2026
@grypez grypez force-pushed the grypez/bringing-in-the-sheaves branch from 4123110 to 2519237 Compare March 10, 2026 14:28
Base automatically changed from grypez/schema-dunder to main March 10, 2026 16:59
@grypez grypez force-pushed the grypez/bringing-in-the-sheaves branch from 0a7b40c to f4bb458 Compare March 10, 2026 22:07
@grypez grypez force-pushed the grypez/bringing-in-the-sheaves branch from f4bb458 to f59d51b Compare April 1, 2026 15:25
@grypez grypez force-pushed the grypez/bringing-in-the-sheaves branch 4 times, most recently from 342233d to 0de9c94 Compare April 27, 2026 13:52
@grypez grypez changed the base branch from main to grypez/evm-wallet-ses-cleanup April 27, 2026 13:53
Base automatically changed from grypez/evm-wallet-ses-cleanup to main April 27, 2026 13:59
@grypez grypez force-pushed the grypez/bringing-in-the-sheaves branch from 0de9c94 to 4f47c89 Compare April 27, 2026 15:25
@grypez grypez marked this pull request as ready for review April 27, 2026 15:51
@grypez grypez requested a review from a team as a code owner April 27, 2026 15:51
Comment thread packages/sheaves/src/sheafify.ts
Comment thread packages/sheaves/src/sheafify.ts
Comment thread packages/sheaves/src/sheafify.ts
Comment thread packages/sheaves/src/sheafify.ts
Copy link
Copy Markdown
Member

@rekmarks rekmarks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a promising direction for composing object capabilities that we should continue to experiment with. Some notes:

  • The terminology is abstruse for the algebraic topologically challenged. I propose an alternative here
  • Sheaves should be in their own package @metamask/sheaves. We'll have to cut a release once this is merged.

@grypez grypez force-pushed the grypez/bringing-in-the-sheaves branch 2 times, most recently from 2bd354e to 62f7a94 Compare May 7, 2026 19:31
@grypez
Copy link
Copy Markdown
Contributor Author

grypez commented May 7, 2026

I think this is a promising direction for composing object capabilities that we should continue to experiment with. Some notes:

  • The terminology is abstruse for the algebraic topologically challenged. I propose an alternative here
  • Sheaves should be in their own package @metamask/sheaves. We'll have to cut a release once this is merged.

Suggestions applied, except any renaming suggestion that included 'tags' in the new name.

@grypez grypez force-pushed the grypez/bringing-in-the-sheaves branch 2 times, most recently from 5c536ca to eed028e Compare May 8, 2026 11:33
@grypez grypez added no-changelog Indicates that no changelog updates are required, and that related CI checks should be skipped. labels May 8, 2026
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
grypez and others added 26 commits May 8, 2026 07:57
…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>
…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>
…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>
@grypez grypez force-pushed the grypez/bringing-in-the-sheaves branch from eed028e to cc25c7f Compare May 8, 2026 11:57
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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 cc25c7f. Configure here.

Comment thread packages/sheaves/src/sheafify.ts Outdated
grypez and others added 2 commits May 8, 2026 12:23
…eMetadata

Two bugs in decomposeMetadata:
1. `key in constraints` matches prototype-inherited names (e.g. 'constructor')
   on an empty {} object, causing distinguishing metadata keys to be silently
   dropped from stripped candidates.
2. `key in meta` matches prototype-inherited names, and when the inherited
   value happens to equal Object.is the candidate value, the key is wrongly
   treated as shared and placed in constraints.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pe chain

Replace `key in obj` with `Object.hasOwn(obj, key)` in two places inside
decomposeMetadata:

- Sharing check (line 127): `key in meta` would pass for prototype-inherited
  names (e.g. 'constructor'), causing a key to be treated as present in a
  candidate that does not own it. If the inherited value also happens to match
  via Object.is, the key is wrongly promoted to a shared constraint.

- Stripping step (line 137): `key in constraints` is true for prototype
  property names on an empty {} object, so any metadata key that shadows a
  prototype name gets silently dropped from stripped candidates even when it
  was never added to constraints.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-changelog Indicates that no changelog updates are required, and that related CI checks should be skipped.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants