feat(kernel-utils): add sheaf programming module#870
Conversation
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
0de9c94 to
4f47c89
Compare
rekmarks
left a comment
There was a problem hiding this comment.
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.
2bd354e to
62f7a94
Compare
Suggestions applied, except any renaming suggestion that included 'tags' in the new name. |
5c536ca to
eed028e
Compare
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>
…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>
eed028e to
cc25c7f
Compare
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 cc25c7f. Configure here.
| const stripped = candidates.map((entry) => { | ||
| const remaining: Record<string, unknown> = {}; | ||
| for (const [key, val] of Object.entries(entry.metadata)) { | ||
| if (!(key in constraints)) { |
There was a problem hiding this comment.
Prototype chain causes incorrect metadata stripping in decompose
Medium Severity
The decomposeMetadata function uses key in constraints to decide which metadata keys to keep on stripped candidates. Because constraints is a plain {} inheriting from Object.prototype, any metadata key matching a prototype property name (e.g., constructor, toString, valueOf, hasOwnProperty) will be incorrectly treated as a constraint and silently dropped from all candidates — even when it was never added to constraints and carries distinguishing values. The policy then receives candidates missing that metadata, leading to incorrect dispatch decisions. Using Object.hasOwn(constraints, key) instead of key in constraints would fix this.
Reviewed by Cursor Bugbot for commit cc25c7f. Configure here.


Introduce operational presheaf + sheafify for guard-based dispatch:
Note
Medium Risk
Adds a brand-new authority-management/routing library (
sheafify) that controls which capability handler is invoked at runtime based on guards and policy, so correctness issues could affect dispatch behavior. Risk is mitigated by being a new, isolated package with extensive unit/e2e coverage.Overview
Introduces a new publishable package,
@metamask/sheaves, providing guard-based capability dispatch viasheafify({ name, providers, compartment? }), including metadata evaluation (constant/callable/source), candidate collapsing by metadata equivalence, constraint/option decomposition, and a policy-driven retry loop with accumulated error history.Adds policy composition utilities (
noopPolicy,withFilter,withRanking,fallthrough,proxyPolicy), remote-provider wrapping (makeRemoteSection), guard union/asyncification utilities, and comprehensive unit/e2e tests plus documentation and package/build wiring (tsconfig references, vitest config, typedoc, yarn workspace entry, licenses/changelog/README).Reviewed by Cursor Bugbot for commit cc25c7f. Bugbot is set up for automated code reviews on this repo. Configure here.