-
Notifications
You must be signed in to change notification settings - Fork 80
feat(compartment-mapper)!: new hooks & type overhaul #2988
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: boneskull/force-load
Are you sure you want to change the base?
feat(compartment-mapper)!: new hooks & type overhaul #2988
Conversation
6c51178 to
d2e827c
Compare
d2e827c to
ee56c40
Compare
|
ee56c40 to
5e78f40
Compare
|
c37edbe to
1d864b5
Compare
8b5ac3e to
38e98bf
Compare
The `PolicyItem` type was having compatibility issues with `SomePackagePolicy`; this changes `PolicyItem` to a non-distributive conditional such that a type argument of `void` defaults to the "base" type. End-users can still provide type arguments for custom `PolicyItem`s.
- Adds `src/guards.js` which will be used elsewhere (not just tests) - Adds `test/snapshot-utilities.js` which provides utilities for sanitizing snapshots - Update `ProjectFixture` to have an optional `entrypoint` property
This fixes `@endo/env-options` so that other packages can access its shipped type declarations.
…compartment-mapper
This commit introduces a comprehensive universal hook system for `@endo/compartment-mapper`, enabling extensible customization of module mapping, bundling, and policy enforcement. BREAKING CHANGES: `CompartmentDescriptor` objects no longer have a `path` property and the `label` property is now a canonical name. Types have changed dramatically. Enhanced validation of `CompartmentMapDescriptor` objects. ## Core Hook Infrastructure ### New Hook System Components: - `src/hooks.js`: Universal hook executor and configuration management - `src/types/hooks.ts`: Comprehensive TypeScript definitions for all hook types - Hook definitions for: `mapNodeModules`, `importLocation`, `link`, `loadLocation`, `digestCompartmentMap`, `captureFromMap`, & `loadFromMap` (many of these are shared) ### Hook Executor Features: - Support for single hooks, hook arrays (pipelines), and phased hooks (pre/post) - Type-safe hook parameter validation and return type checking - Error handling with wrapped exceptions for debugging - Default hook configuration merging and application - Logging integration for hook execution tracing ## Hook Integration Points ### `mapNodeModules` Hooks: - `packageDescriptor`: Called after parsing each package.json with package metadata - `packageDependencies`: Called for each package with its dependency list (supports filtering/modification) - `unknownCanonicalName`: Called when policy references unknown packages for validation/reporting ### `link` Hooks (available where `link()` is called): - `moduleSource`: Called when module source objects are created (either from local files containing `bytes`, exit modules, or "error-type" sources) ### `captureFromMap` Hooks - `packageConnections`: Called during digest; surfaces "connections" between compartment descriptors ## Type System Overhaul (`src/types/`) ### Major Breaking Changes in `compartment-map-schema.ts`: #### CompartmentDescriptor Interface Restructure: - **BREAKING**: Removed `path` property - compartment descriptors no longer track dependency paths - **BREAKING**: `label` property type changed from `string` to `CanonicalName<U>` - labels are now type-safe canonical names - **BREAKING**: Made `CompartmentDescriptor` generic with `<T extends ModuleDescriptorConfiguration, U extends string>` for better type safety - Added `location: string` as a required property for all compartment descriptors - Added optional `sourceDirname` as found in the sources #### CompartmentMapDescriptor Genericization: - **BREAKING**: `CompartmentMapDescriptor` is now generic: `CompartmentMapDescriptor<T, Name, EntryName>` - **BREAKING**: `compartments` property changed from `Record<string, CompartmentDescriptor>` to `CompartmentDescriptors<T, Name>` - **BREAKING**: `entry` property type changed from `EntryDescriptor` to `EntryDescriptor<EntryName>` - New specialized types: `PackageCompartmentMapDescriptor`, `FileCompartmentMapDescriptor`, and `DigestedCompartmentMapDescriptor` #### Enhanced Module Descriptor System: - `ModuleDescriptor` is now `ModuleDescriptorConfiguration` to differentiate between it & the `ModuleDescriptor` from `ses` - Added `ModuleDescriptorConfigurationCreator` enum tracking module creation source (`'link' | 'transform' | 'import-hook' | 'digest' | 'node-modules'`) - Enhanced `BaseModuleDescriptorConfiguration` with `__createdBy` property for debugging - Added `ErrorModuleDescriptorConfiguration` for deferred error handling - Improved type discrimination with `ModuleDescriptorConfigurationKindToType` utility type ### New Type Infrastructure: #### `canonical-name.ts` - Canonical Name Type System: - **NEW**: Type-level canonical name validation using template literal types - `CanonicalName<S>` - validates npm package name chains separated by '>' (e.g., "foo>bar", "@scope/pkg>dep") - `ScopedPackageName` and `UnscopedPackageName` for npm package name validation - `SplitOnGt<S>` and `AllValidPackageNames<Parts>` for compile-time canonical name parsing - `IsCanonicalName<S>` predicate type for conditional type logic #### `hooks.ts` - Universal Hook Type System: - `HookFn<TInput>` - generic hook function with input/output type safety - `HookConfiguration<T>` - utility type making all hook properties optional with pipeline support - `HookExecutorFn<T>` - type-safe hook executor supporting both regular and phased hooks - `PhasedHookDefinition` - pre/post hook phase support with type safety - Comprehensive hook definitions for all integration points: - `MapNodeModulesHooks` - package discovery and dependency processing - `MakeImportHookMakerHooks` - module source processing - `LinkHooks` - module linking operations - `LoadLocationHooks` - location loading operations - `DigestCompartmentMapHooks` - compartment map digestion - `CaptureFromMapHooks` - compartment map capture operations ### Enhanced Type Safety Features: #### Compartment Descriptor Validation: - `DigestedCompartmentDescriptor` - restricted descriptor for archived compartment maps - Properties marked as `never` for digested descriptors: `path`, `retained`, `scopes`, `parsers`, `types`, `__createdBy`, `sourceDirname` - `CompartmentDescriptorWithPolicy<T>` - enforces policy presence where required #### Module Configuration Type Safety: - `ModuleDescriptorConfigurationKind` union for module type discrimination - Type-safe module configuration creators with `__createdBy` tracking #### Policy Integration: - Enhanced policy types in `PackageCompartmentDescriptor` with canonical name constraints - `LiteralUnion` usage for special canonical names (`ATTENUATORS_COMPARTMENT`, `ENTRY_COMPARTMENT`) - Policy-aware compartment descriptor types with enhanced validation ### Type System Utilities: #### `typescript.ts` Enhancements: - Enhanced `LiteralUnion<L, B>` for better literal type preservation - Type utilities supporting the new generic compartment map architecture - Moved `Simplify` from tests here (because it's useful dammit) #### `external.ts` Hook Integration: - Re-exported all hook types from `hooks.ts` for public API - Enhanced options types with hook configuration support ## Comprehensive Test Coverage ### New Test Files: - `test/hook-executor.hooks.test.js`: Core hook executor functionality - `test/map-node-modules.hooks.test.js`: `mapNodeModules` hook integration - `test/make-import-hook-maker.hooks.test.js`: `makeImportHookMaker` hook integration - `test/apply-hook-defaults.hooks.test.js`: Hook configuration merging and defaults - [ ] TODO: `test/capture-from-map.hooks.test.ts`: `captureFromMap` hook integration ## Enhanced Policy Validation ### Policy Validation: - Unknown canonical name detection with suggestion system - Cross-reference policy resources against actual compartment contents - Detailed error reporting with path information for policy issues - Hook integration for custom policy validation logic ### Improved Error Handling: - Wrapped hook errors with context information - Detailed path reporting for policy validation failures - Suggestion system for likely intended canonical names
- This hook just dumps all known canonical names. - The `unknownCanonicalName` hook has changed its parameters.
I don't think we're going to need this; removing it for now since it just adds more complexity.
b21326c to
983ad2e
Compare
a978be1 to
7916a39
Compare
7916a39 to
d73f3d9
Compare
| label, | ||
| { policy, packagePolicy = undefined } = {}, | ||
| ) => { | ||
| if (packagePolicy !== undefined) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how would a package policy be already known but still go through this function? What is the point of doing it here instead of checking at call location?
| } else { | ||
| defaultAttenuator = | ||
| compartmentDescriptors[ATTENUATORS_COMPARTMENT].policy.defaultAttenuator; | ||
| // TODO: the attenuators compartment must always have a non-empty policy; maybe throw if it doesn't |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sounds good
| message, | ||
| log, | ||
| }) => { | ||
| log(`WARN: Invalid resource ${q(canonicalName)} in policy: ${message}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This kind of things, at least in my experience with webpack, worked better if aggregated and displayed as a single warning. In a debug mode of sorts they could be used to generate proposed policy fixes.
| const DefaultCompartment = Compartment; | ||
|
|
||
| /** | ||
| * TODO: can we just use `Object.hasOwn` instead? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a new addition to SES AFAIR
| } | ||
|
|
||
| // #region packageDescriptor hook | ||
| if (packageDescriptorHook) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this is useful, but it could be useful if it allowed altering the descriptor (eg. in case it contain something that was not supported or needed a fix.
| Compartment: CompartmentOption = DefaultCompartment, | ||
| log = noop, | ||
| preload = [], | ||
| packageConnectionsHook, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
packageDependenciesHook and packageConnectionsHook are similar
I looked at both of these hooks and I think packageConnectionsHook could be replaced with packageDependenciesHook and the main difference is that one of them allows returning a mutated set.
If we could offer the same in digest, we'd have a single hook for both.
…riptorHook into a single packageData hook
Refs: #2929
This PR supersedes #2929 which should now be considered abandoned.
This PR targets #2915, if you didn't notice.
Description
This PR is "complete" and ready for review. It makes a significant overhaul to types and adds new options to several public APIs.
Description of changes to Types
CompartmentMapDescriptorhave been vastly expanded to reflect the different "flavors" ofCompartmentMapDescriptor,CompartmentDescriptor,ModuleDescriptor(nowModuleDescriptorConfigurationto differentiate it fromses'ModuleDescriptor), and the formatting of the compartment names (keys).CompartmentDescriptor.labelis now a canonical name.CompartmentDescriptor.pathis removedCompartmentDescriptor.compartmentsis removedNew Default Behavior in
mapNodeModules()Currently,
mapNodeModules()avoids adding aModuleDescriptor(ModuleDescriptorConfiguration) to a nascentCompartmentDescriptor'smodulesprop if policy disallows it. This PR changes the behavior to consider policy & excise the dependency from theNodeitself before the (dependency)Graphis translated into aCompartmentDescriptor. This has implications around what sort of errors are thrown when Compartment A attempts to load a Compartment B to which it has no access (see note about policy tests below).Other Notes
CompartmentDescriptor.compartmentshas been removed, that means implicitly allowing dynamic requires of parent/ancestor Compartments from a given Compartment via absolute path is no longer supported. That's fine, since:@lavamoat/nodewas the only user of this feature and no longer needs it$root$. It is valid withinPackagePolicy; i.e. some other package can be allowed to access the$root$compartment.test/policy.test.tsneeded to change because of howmapNodeModules'packageDependencieshook works. Instead of rejecting module descriptors (ModuleDescriptorConfigurations) based on policy, we reject entire dependencies before they can be "digested" into module descriptors. This results inScopeDescriptors not being populated, in addition toModuleDescriptorConfigurations, and causes downstream effects. Different exceptions are thrown at different times, but the intent of these tests doesn't deviate from "ensure this naughty behavior is not allowed."Security Considerations
None that I'm aware of.
Scaling Considerations
Any consumer providing a resource-intensive blocking hook to
@endo/compartment-mappergets the performance they deserve.Documentation Considerations
This is a breaking change to the contents of the archives as well as a breaking change to the types. It is not a breaking change to the archive format, but it will contain different values (instead of
packageName-v<version>the compartment descriptors will be keyed on the canonical name).Testing Considerations
Compatibility Considerations
The breaking changes mentioned above, but it also solves like ten different problems
@lavamoat/nodewas having trying to get things to work. It significantly improves ecosystem compatibility and will improve the performance of@lavamoat/node.Upgrade Considerations
*BREAKING*:in the commit message with migration instructions for any breaking change.NEWS.mdfor user-facing changes.This commit introduces a comprehensive universal hook system for
@endo/compartment-mapper,enabling extensible customization of module mapping, bundling, and policy enforcement.
BREAKING CHANGES:
CompartmentDescriptorobjects no longer have apathproperty and thelabelproperty is now a canonical name. Types have changed dramatically. Enhanced validation ofCompartmentMapDescriptorobjects.New Options
mapNodeModules()packageDataHook: Called once beforetranslateGraphwith data about all packages found while crawlingnode_modules. Receives a read-onlyMap<CanonicalName, PackageData>where each entry contains:name: Package namepackageDescriptor: The package.json contentslocation: File URL to the packagecanonicalName: The canonical name used in policypackageDependenciesHook: Called for each package's dependencies during graph translation, allowing dynamic modification of the dependency graph. Can add or remove dependencies from packages based on policy or other criteria.unknownCanonicalNameHook: Called when policy references canonical names that don't exist in the dependency graph, with optional suggestions for typos/similar names.makeImportHookMaker()moduleSourceHook(): Called when module source objects are created (either from local files containingbytes, exit modules, or "error-type" sources)captureFromMap()packageConnectionsHook: Called during digest; surfaces "connections" between compartment descriptorsType System Overhaul (
src/types/)Major Breaking Type Changes in
compartment-map-schema.ts:CompartmentDescriptor Interface Restructure:
pathproperty - compartment descriptors no longer track dependency pathslabelproperty type changed fromstringtoCanonicalName<U>- labels are now type-safe canonical namesCompartmentDescriptorgeneric with<T extends ModuleDescriptorConfiguration, U extends string>for better type safetylocation: stringas a required property for all compartment descriptorssourceDirnameas found in the sourcesCompartmentMapDescriptor Genericization (Is That A Word? No):
CompartmentMapDescriptoris now generic:CompartmentMapDescriptor<T, Name, EntryName>compartmentsproperty changed fromRecord<string, CompartmentDescriptor>toCompartmentDescriptors<T, Name>entryproperty type changed fromEntryDescriptortoEntryDescriptor<EntryName>PackageCompartmentMapDescriptor,FileCompartmentMapDescriptor, andDigestedCompartmentMapDescriptorEnhanced Module Descriptor System:
ModuleDescriptoris nowModuleDescriptorConfigurationto differentiate between it & theModuleDescriptorfromsesModuleDescriptorConfigurationCreatorenum tracking module creation source ('link' | 'transform' | 'import-hook' | 'digest' | 'node-modules')BaseModuleDescriptorConfigurationwith__createdByproperty for debuggingErrorModuleDescriptorConfigurationfor deferred error handlingModuleDescriptorConfigurationKindToTypeutility typeNew Type Infrastructure:
canonical-name.ts- Canonical Name Type System:CanonicalName<S>- validates npm package name chains separated by '>' (e.g., "foo>bar", "@scope/pkg>dep")ScopedPackageNameandUnscopedPackageNamefor npm package name validationSplitOnGt<S>andAllValidPackageNames<Parts>for compile-time canonical name parsingIsCanonicalName<S>predicate type for conditional type logicEnhanced Type Safety Features:
Compartment Descriptor Validation:
DigestedCompartmentDescriptor- restricted descriptor for archived compartment mapsneverfor digested descriptors:path,retained,scopes,parsers,types,__createdBy,sourceDirnameCompartmentDescriptorWithPolicy<T>- enforces policy presence where requiredModule Configuration Type Safety:
ModuleDescriptorConfigurationKindunion for module type discrimination__createdBytrackingPolicy Integration:
PackageCompartmentDescriptorwith canonical name constraintsLiteralUnionusage for special canonical names (ATTENUATORS_COMPARTMENT,ENTRY_COMPARTMENT)Type System Utilities:
typescript.tsEnhancements:LiteralUnion<L, B>for better literal type preservationSimplifyfrom tests here (because it's useful dammit)Enhanced Policy Validation
Policy Validation: