Conversation
|
This change is part of the following stack: Change managed by git-spice. |
🦋 Changeset detectedLatest commit: 37aa50b The changes in this PR will be included in the next version bump. This PR includes changesets to release 19 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Improves TypeScript type inference and TS6 compatibility across multiple Endo packages, primarily via type-level refinements and expanded type tests.
Changes:
- Refines
@endo/patternstype inference (e.g.,M.remotable()default,voidvsundefined,PromiseLike, optional tuples, rest args, and newCastedPattern<T>). - Updates
@endo/exoguarded type plumbing to preserve facet inference and improveGuarded<..., G>structural compatibility. - Parameterizes OCapN CapTP slot types and adds a new
@endo/commonhelperobjectExtendEach.
Reviewed changes
Copilot reviewed 27 out of 32 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/ses/types.d.ts | Adds global typing for ModuleSource (optionally installed by shim). |
| packages/ses/test/module-source.test.js | Narrows ModuleSource to non-undefined after shim load for tests. |
| packages/patterns/test/types.test-d.ts | Expands/updates type-level regression tests (void/PromiseLike/remotable/rest/optional tuples/CastedPattern). |
| packages/patterns/src/types.ts | Adds CastedPattern<T> and changes M.remotable() default generic to any; tightens interface guard generic constraints. |
| packages/patterns/src/type-from-pattern.ts | Implements CastedPattern handling and multiple inference fixes (void/PromiseLike/TFOr/TFSplitRecord/TFOptionalTuple/TFRestArgs/etc.). |
| packages/pass-style/src/types.d.ts | Changes CopyArray<T> to readonly T[] to accept readonly tuples. |
| packages/ocapn/test/codecs/passable.test.js | Adds casts to satisfy stricter typing for tagged passable codec tests. |
| packages/ocapn/test/client.test.js | Adds casts for debug/message typing in tests. |
| packages/ocapn/test/captp/pairwise.test.js | Adds casts for primitive-path slot lookup tests. |
| packages/ocapn/src/client/sturdyrefs.js | Casts bootstrap to any for E(...) call site under stricter typing. |
| packages/ocapn/src/client/ref-kit.js | Adds JSDoc assertions for remote promise/object value lookups. |
| packages/ocapn/src/client/ocapn.js | Widens getRemoteBootstrap return type to any. |
| packages/ocapn/src/captp/types.js | Parameterizes CapTP Slot type by SlotType. |
| packages/ocapn/src/captp/pairwise.js | Propagates parameterized Slot<T> through makeSlot/parseSlot. |
| packages/exo/types-index.d.ts | Adjusts generics to avoid facet inference collapse and improve ThisType inference in kits. |
| packages/exo/test/types-plain-unguarded.test-d.ts | Adds regression tests for facet inference when guard kit is undefined. |
| packages/exo/test/types-plain-guarded.test-d.ts | Updates expectations to void returns and documents kit typing trade-offs. |
| packages/exo/test/types-advanced.test-d.ts | Updates advanced type expectations (e.g., void returns). |
| packages/exo/test/heap-classes.test.js | Removes @ts-expect-error markers that are no longer expected under new typing. |
| packages/exo/src/types.d.ts | Makes Guarded<M, G> structurally compatible across G via phantom method; adjusts GuardedMethods extraction. |
| packages/eventual-send/test/eventual-send.test.js | Casts rwp(handler2) result to any under stricter checking. |
| packages/eventual-send/src/no-shim.js | Removes an imported type from JSDoc @import list. |
| packages/eventual-send/src/exports.d.ts | Exports additional public E() helper types. |
| packages/eventual-send/src/E.js | Short-circuits any in several conditional helper types to avoid unusable projections. |
| packages/daemon/src/context.js | Tightens JSDoc typing (@returns, Context['cancel']) and asserts returned object type. |
| packages/compartment-mapper/src/link.js | Removes an unused JSDoc-imported type name. |
| packages/common/object-map.js | Adds new objectExtendEach helper with hardened result and typed JSDoc. |
| .eslintignore | Removes explicit un-ignore entries for many .d.ts files. |
| .changeset/ocapn-captp-slot-types.md | Changeset for OCapN slot typing improvements. |
| .changeset/improve-pattern-exo-inference.md | Changeset for patterns/exo/pass-style inference improvements. |
| .changeset/eventual-send-type-exports.md | Changeset for eventual-send type exports and any short-circuiting. |
| .changeset/common-object-extend-each.md | Changeset for new objectExtendEach helper. |
Comments suppressed due to low confidence (1)
packages/patterns/src/type-from-pattern.ts:1
- The doc comment says
M.remotable<SomeTypedef>()should preserve and return the concretePayloadtype, but the implementation currently returnsanyfor all non-InterfaceGuardpayloads. This makes parameterizedM.remotable<T>()lose specificity and contradicts the comment (and parts of the PR description/changeset). Consider returningPayloadin the non-InterfaceGuardbranch, and using a separate condition to keep the unparameterized default (any) behavior.
/// <reference types="ses"/>
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /** | ||
| * Map a tuple to elements that may be undefined (approximates optional). | ||
| * Map a tuple of patterns to a tuple of inferred types whose elements are | ||
| * truly optional (`[X?, Y?]`), not just `T | undefined`. | ||
| * | ||
| * TS limitation: We cannot produce `[X?, Y?]` from a recursive conditional | ||
| * type — `Partial<Tuple>` only works on concrete tuples. For splitArray | ||
| * optional elements we use `T | undefined` instead, meaning the array | ||
| * must still be the full length. If TS gains support for producing truly | ||
| * optional tuple elements from conditional types, this should be revised. | ||
| * Uses a homomorphic mapped type with the `?` modifier | ||
| * (`{ [K in keyof T]?: ... }`). Despite an earlier comment claiming this | ||
| * was impossible, TypeScript *does* support producing optional tuple elements | ||
| * from a homomorphic mapped type — the `?` modifier on the mapping projects | ||
| * to truly-optional positions when the source `T` is a tuple type. | ||
| * | ||
| * The `& {}` removes the `length` and `Array.prototype` keys that the | ||
| * mapped type would otherwise keep, leaving a clean tuple shape that | ||
| * matches consumer typedefs declared with `[X?, Y?]` syntax. | ||
| */ | ||
| type TFOptionalTuple<T extends readonly any[]> = T extends readonly [ | ||
| infer H, | ||
| ...infer R, | ||
| ] | ||
| ? [TypeFromPattern<H> | undefined, ...TFOptionalTuple<R>] | ||
| : []; | ||
| type TFOptionalTuple<T extends readonly any[]> = { | ||
| [K in keyof T]?: TypeFromPattern<T[K]>; | ||
| }; |
There was a problem hiding this comment.
Good catch — the comment was stale. The & {} intersection is not needed and not present: TypeScript's homomorphic-mapped-type-over-tuple special case preserves tuple-ness on its own, so { [K in keyof T]?: ... } already produces a clean [X?, Y?] shape without length/prototype pollution. The test at types.test-d.ts:402 pins this down with expectType<[string, bigint?, boolean?]> (exact-type match), which the current impl passes.
Updated the doc comment to drop the misleading paragraph and explain the preservation via the homomorphic-mapped-type special case instead.
There was a problem hiding this comment.
Good catch — the comment was stale. The & {} intersection is not needed and not present: TypeScript's homomorphic-mapped-type-over-tuple special case preserves tuple-ness on its own, so { [K in keyof T]?: ... } already produces a clean [X?, Y?] shape without length/prototype pollution. The test at types.test-d.ts:402 pins this down with expectType<[string, bigint?, boolean?]> (exact-type match), which the current impl passes.
Updated the doc comment to drop the misleading paragraph and explain the preservation via the homomorphic-mapped-type special case instead.
| * @template {Record<string, unknown>} O | ||
| * @template {Record<string, unknown>} Ex | ||
| * @param {O} original | ||
| * @param {(value: O[keyof O & string], key: string & keyof O) => Ex} extendFn | ||
| * @returns {{ [K in keyof O]: O[K] & Ex }} | ||
| */ | ||
| export const objectExtendEach = (original, extendFn) => { | ||
| const newEntries = typedMap( | ||
| typedEntries(original), | ||
| /** @type {([k, v]: [string, object]) => [string, object]} */ | ||
| ([k, v]) => [k, { ...v, ...extendFn(v, k) }], | ||
| ); | ||
| return /** @type {any} */ (harden(fromTypedEntries(newEntries))); | ||
| }; |
boneskull
left a comment
There was a problem hiding this comment.
This is pretty big, but I went over it about as well as can be expected. I don't see any blockers; just some questions and suggestions.
| case 'get': { | ||
| const noGet = new HandledPromise((_, _2, rwp) => { | ||
| const obj = rwp(handler2); | ||
| const obj = /** @type {any} */ (rwp(handler2)); |
There was a problem hiding this comment.
would you prefer a ts-expect-error? it is just a test
| > = StripIndexSignature<M> & { | ||
| __getInterfaceGuard__?: () => G | undefined; | ||
| __getInterfaceGuard__?: () => InterfaceGuard | undefined; | ||
| __interfaceGuard__?(g?: G): void; |
There was a problem hiding this comment.
Should this be:
| __interfaceGuard__?(g?: G): void; | |
| __interfaceGuard__?(g?: G | undefined): void; |
..?
There was a problem hiding this comment.
not sure. what drives the question?
| */ | ||
| export const parseSlot = slot => { | ||
| const type = slot[0]; | ||
| const type = /** @type {T} */ (slot[0]); |
There was a problem hiding this comment.
This is interesting and makes me think about how to avoid the type assertion. I don't think it's avoidable. That said, it should be possible to create a type and function which would extract the SlotType from any Slot (though the function itself would contain a type assertion).
type ExtractSlotType<Slot extends string> = Slot extends
`${infer S extends SlotType}${'+' | '-'}${string}` ? S : never;
function extractSlotType<T extends Slot<SlotType>>(slot: T): ExtractPrefix<T> {
return s[0] as ExtractPrefix<T>;
}Of course, this is of dubious value, but I just got nerd-sniped, so I'm sharing.
| provideRemotePromiseValue: position => { | ||
| const slot = makeSlot('p', false, position); | ||
| let value = ocapnTable.getValueForSlot(slot); | ||
| let value = /** @type {Promise<unknown> | undefined} */ ( |
There was a problem hiding this comment.
good catch. from an earlier rev. removing
| // produced). TypeScript's distinction between `void` and `undefined` | ||
| // is that a `void`-returning function may actually return any value | ||
| // (callers must ignore it), while an `undefined`-returning function | ||
| // must return literally `undefined`. At a parameter/return position |
| new (source: string, opts?: string | object): ModuleSourceInstance; | ||
| readonly prototype: ModuleSourceInstance; | ||
| } | ||
| var ModuleSource: ModuleSourceConstructor | undefined; |
There was a problem hiding this comment.
Why isn't it @endo/module-source's responsibility to add this global?
There was a problem hiding this comment.
Good question. Its shim does add the global. I think it was because this module defines other globals but I've moved it to module-source.
| !packages/bundle-source/src/exports.d.ts | ||
| !packages/captp/src/ts-types.d.ts | ||
| !packages/cli/test/_types.d.ts | ||
| !packages/compartment-mapper/src/types-external.d.ts | ||
| !packages/compartment-mapper/src/types.d.ts | ||
| !packages/daemon/src/types.d.ts | ||
| !packages/daemon/types.d.ts | ||
| !packages/eventual-send/src/exports.d.ts | ||
| !packages/eventual-send/src/types.d.ts | ||
| !packages/exo/src/types.d.ts | ||
| !packages/far/src/exports.d.ts | ||
| !packages/lp32/types.d.ts | ||
| !packages/pass-style/src/types.d.ts | ||
| !packages/ses/src/reporting-types.d.ts | ||
| !packages/ses/types.d.ts | ||
| !packages/stream/types.d.ts | ||
| !packages/trampoline/types.d.ts | ||
| !packages/where/types.d.ts |
There was a problem hiding this comment.
landed this in the TS6 bump
| // Required + optional: produces truly optional tuple elements `[X?, Y?]` | ||
| // (not just `T | undefined`). The optional positions in `splitArray`'s | ||
| // second argument become positions you can omit from the call site, | ||
| // matching consumer typedefs like `TransferPart = [a?, b?, c?, d?]`. |
There was a problem hiding this comment.
What changed so that this claim is now true?
There was a problem hiding this comment.
Commit 1155ec691 ("fix(patterns): TFOptionalTuple emits truly optional elements") changed TFOptionalTuple from a recursive conditional type to a homomorphic mapped type.
Before:
type TFOptionalTuple<T extends readonly any[]> = T extends readonly [infer H, ...infer R]
? [TypeFromPattern<H> | undefined, ...TFOptionalTuple<R>]
: [];Produced [X | undefined, Y | undefined] — each position a union with undefined, not optional. Callers had to pass all N elements with explicit undefined placeholders.
After:
type TFOptionalTuple<T extends readonly any[]> = {
[K in keyof T]?: TypeFromPattern<T[K]>;
};Produces [X?, Y?] — truly optional positions callers can omit.
The prior code comment claimed TS couldn't produce [X?, Y?] from a recursive conditional. That's technically true (conditional types can't emit optional positions) but the wrong framing — the fix swaps tools from conditional recursion to a homomorphic mapped type, where the ? modifier attaches to each position rather than the value type. When T is a tuple, { [K in keyof T]?: ... } iterates the tuple indices and preserves tuple-ness; the tuple-ness of the input is load-bearing.
The test assertion at this line was updated in the same commit from [X | undefined, Y | undefined] to [X, Y?, Z?].
| { | ||
| const innerShape = M.string(); | ||
| // A pattern value can be cast to CastedPattern<T> at the type level | ||
| const casted = innerShape as unknown as CastedPattern<'agoric1xyz'>; | ||
| type T = TypeFromPattern<typeof casted>; | ||
| expectType<'agoric1xyz'>(null as unknown as T); | ||
| } |
There was a problem hiding this comment.
This seems dubious. Why even declare innerShape if we throw away the type? Why use typeof casted when we could use CastedPattern<'agoric1xyz'> instead?
There was a problem hiding this comment.
Good catch. I've tightened up the test to verify CastedPattern's phantom is optional.
const casted: CastedPattern<'agoric1xyz'> = innerShape;
| setData(val: string) { | ||
| expectType<string>(val); |
There was a problem hiding this comment.
Given the val: string, the assertion is pretty useless.
Three independent fixes discovered while integrating with Agoric SDK:
1. **pass-style: CopyArray accepts readonly arrays.**
`CopyArray<T>` was `Array<T>`, rejecting readonly tuples like
`readonly ["ibc"]`. All passables are hardened at runtime, so the
type should reflect immutability. Changed to `readonly T[]`. This is
backward-compatible since `Array<T>` extends `ReadonlyArray<T>`,
so existing mutable arrays remain assignable, but readonly tuples
now also satisfy `Passable`.
2. **patterns: M.remotable() default and TFRemotable fallback are `any`.**
Previously, `M.remotable<T extends Passable = RemotableObject |
RemotableBrand<any, any>>()` resolved unparameterized remotables to
a type that wasn't assignable to concrete remotable typedefs (like
StorageNode in agoric-sdk). The default is now `any`, matching
`M.promise<T = any>()`.
We could express the tighter constraint "object with only methods,
no data" as `Record<PropertyKey, (...args: any[]) => any>`, but
TypeScript index signatures don't satisfy specific named properties
(`Record<string, F>` is not assignable to `{ foo: F }`), so it
doesn't help downstream consumers without forcing casts at every
use site. `any` is the pragmatic choice; the parameterized form
`M.remotable<typeof SomeInterfaceGuard>()` still provides precise
inference when needed.
3. **exo: defineExoClassKit removes `& Methods` from facet constraint.**
The constraint
`F extends { [K in keyof GK]: TypeFromInterfaceGuard<GK[K]> & Methods }`
caused TypeScript to absorb specific facet method names into the
`string | number | symbol` index signature (`'view' | string`
collapses to `string`), making `FilteredKeys` return `never` and
producing `Pick<X, never> = {}`. Result: callers like
`E(viewCounter).view()` failed with "Property 'view' does not
exist", because the facet's methods were collapsed to an empty
record.
Removing `& Methods` preserves the concrete method names while
still enforcing guard-method compatibility via
`TypeFromInterfaceGuard<GK[K]>` alone.
Updated `types.test-d.ts` to assert the new `M.remotable()` resolution
(`any` instead of `RemotableObject | RemotableBrand<any, any>`).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Same issue as the previous defineExoClassKit fix: the `& Methods`
intersection in the M constraint causes TypeScript to absorb specific
method names into the `string | number | symbol` index signature,
making `keyof M` collapse to `PropertyKey` and `FilteredKeys` in
PickCallable return `never`. Result: `Pick<X, never> = {}`, and
callers like `E(creatorFacet).addAsset()` fail with "Property
'addAsset' does not exist".
The single-facet `defineExoClass` and `makeExo` had the same
constraint pattern as `defineExoClassKit`, just less obvious because
single-facet exos don't always trigger the issue. They DO trigger it
when consumed via `E()` (which goes through `RemoteFunctions` →
`PickCallable` → `FilteredKeys`).
Verified that the strict tests in `types-plain-guarded.test-d.ts`
still catch wrong arg/return types and missing methods — the constraint
`M extends TypeFromInterfaceGuard<G>` is sufficient on its own.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
`.rest(P)` matches the rest portion of the args array (as a single array) against P. Two cases: - If P infers to an array type (e.g. `M.arrayOf(M.string())` → `string[]`), the rest type IS that array — don't wrap. - If P infers to a non-array (e.g. `M.any()` → `Passable`), each individual rest arg matches P, so the rest type is `P[]`. Previously TFRestArgs always wrapped with `[]`, double-wrapping array patterns. For example, `M.call().rest(M.arrayOf(M.string()))` inferred the rest as `string[][]` instead of `string[]`. This affected real code in agoric-sdk's @agoric/vats nameHub: `.rest(PathShape)` where PathShape = M.arrayOf(M.string()) was inferring as `string[][]`, breaking the impl signature `(...path: string[])`. Added regression tests for both cases. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
`CastedPattern<T>` is a Pattern carrying an unchecked static type
assertion via a phantom property. Like a TypeScript `as` cast, it is
trusted by the type system but unverifiable at runtime — the runtime
pattern check still validates the structural shape it always did, while
T expresses a developer claim about the matched values.
Motivating cases (any of which downstream packages can now express
without restructuring their patterns):
1. **Discriminated unions through structural patterns.** A pattern like
`{ brand: BrandShape, value: AmountValueShape }` infers structurally
to the cross-product `{ brand: Brand, value: NatValue | SetValue | ... }`,
not the discriminated union `Amount = NatAmount | SetAmount | ...`.
With `CastedPattern<Amount>`, the inferred type IS the union.
2. **Branded / template literal types.** `M.string()` infers as `string`,
but a guard for an address may want `\`agoric1${string}\``. Casting
the pattern lets the type carry the brand without changing runtime.
3. **Nominal typedef recovery.** Many Agoric typedefs (StorageNode,
Connection, Vow<X>) are plain object types whose structural shape
the inference can't recover from a Matcher. Casting bridges the gap.
`TypeFromPattern` extracts the asserted type when the phantom is set to
a concrete type. The `unknown extends Asserted` check distinguishes a
real cast from an unset phantom (which falls through to the existing
structural inference), so patterns without a cast are unaffected.
The phantom uses a string-constant key rather than `unique symbol` to
avoid declaration-emit issues with downstream packages re-exporting the
type — this is the same approach the deprecated `validatedType` used.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…eGuard When TFRemotable returns Payload directly for non-InterfaceGuard cases, `Payload = any` causes the conditional to distribute. The true branch produces a `Simplify<...>` mapped type that doesn't collapse with the `any` from the false branch — TypeScript shows the structural Passable union, breaking downstream consumers that expected `any`. Reverting this branch to `any` matches `M.promise()`'s default. Users who want to express a specific type should use `CastedPattern<X>` (which TypeFromPattern now handles via a phantom-property check), not the unparameterized `M.remotable<X>()` form. Fixed two regression boundaries (lines 902 and 1058 of types.test-d.ts) that asserted contradictory things; both now agree the default M.remotable() resolves to `any`. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Restructure the `methodsKit` parameter on both `defineExoClassKit`
overloads from a per-key intersection to an outer-level intersection,
and wrap the `facets` self-reference with `NoInfer<...>`.
The previous form
methodsKit: { [K in keyof F]: F[K] & ThisType<{ facets: GuardedKit<F>; ... }> }
prevented TypeScript from back-inferring `F[K]` from the argument
because the per-facet intersection with `ThisType<...>` cannot be
cleanly subtracted during inference. As a result, `F` degraded to its
constraint default `Record<FacetName, Methods>`, and consumers of the
returned kit saw
GuardedKit<Record<string, Methods>, { [x: string]: InterfaceGuard<...> }>
— losing all facet names and method signatures. The new form
methodsKit: F & ThisType<{ facets: NoInfer<GuardedKit<F>>; ... }>
fixes both halves of the problem:
* Lifting `ThisType` outside the mapped type lets TS match the
argument shape against `F` directly. Inference latches onto the
methods literal.
* `NoInfer<GuardedKit<F>>` blocks the `this.facets` self-reference
from re-introducing a circular inference path that would otherwise
collapse `F` back to its constraint default.
`ThisType` propagation into nested facet methods is preserved at any
nesting depth (verified in the new regression test).
Add a regression test in `types-plain-unguarded.test-d.ts` covering
the exact shapes that broke in the wild — an empty facet alongside a
populated one, and direct facet access on a returned kit instance —
both reported in agoric-sdk's `packages/orchestration/src/utils/progress.js`
and `packages/portfolio-contract/src/portfolio.exo.ts`.
Impact in the agoric-sdk monorepo:
yarn typecheck-quick: 293 errors -> 120 errors (-173, -59%)
with zero new errors introduced.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous check `G extends { payload: { argGuard: infer P } }` was
intended to detect `AwaitArgGuard` (whose payload is literally
`{ argGuard: Pattern }`). However, any matcher whose `Payload` type
parameter happens to be `any` — for example `M.array()` which returns
`MatcherOf<'arrayOf'>` (default Payload `any`) — *also* trivially
satisfies that shape test, because `any extends { argGuard: infer P }`
unifies and `infer P` distributes oddly. The result was that arg
positions for `M.array()` (and any other matcher with a wide payload)
were inferred as `unknown` instead of `any[]`.
Fix: gate the structural check behind a `[Symbol.toStringTag]:
'guard:awaitArgGuard'` tag check first, mirroring the discrimination
already used for `'guard:rawGuard'`. This is the canonical TypeScript
pattern for tagged unions: discriminate on the tag rather than on the
payload shape, especially when the payload may be `any`.
Verified by typechecking agoric-sdk's
`packages/orchestration/src/examples/axelar-gmp-account-kit.js` —
`args` in `makeEVMTransactionInvitation(method, args)` is now
correctly inferred as `any[]` instead of `unknown`.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Widen the MethodGuard constraint in InterfaceGuard, InterfaceGuardPayload, MakeInterfaceGuardStrict, and MakeInterfaceGuardGeneral from the bare `MethodGuard` to `MethodGuard<any, any, any, any, any>` (all type parameters set to `any`). When TypeScript contextually types an inline expression like `M.call().returns(M.promise())` against the constraint `Record<PropertyKey, MethodGuard>`, the bare `MethodGuard` defaults its type parameters to their constraints — so `RetGuard` becomes `SyncValueGuard` and `MatcherOf<'promise', any>`'s `Payload` parameter drifts from `any` to a non-canonical unknown form (`void | RawGuardPayload | null`). TypeScript displays the structural equivalent of `unknown` as that three-way union but does not recognize it as `unknown`, so `TypeFromPattern`'s `unknown extends Payload` guard in the `'promise'` branch fails and the wide payload leaks through as the inferred return type. Setting all `MethodGuard` type parameters to `any` in the constraint removes the contextual narrowing pressure: TypeScript no longer tries to fit the inline expression into a narrower default, and the inferred `MatcherOf<'promise', any>` survives with its `any` payload intact. The same fix protects every other `MatcherOf<Tag, T>` whose `T` would otherwise drift through inline expressions in `M.interface(...)` calls. This resolves the `void | RawGuardPayload | null` mystery union documented as XXX tech debt in agoric-sdk's `packages/vats/src/nameHub.js` and `packages/vats/src/bridge.js`, and eliminates 3 errors in agoric-sdk's `packages/fast-usdc-contract/` (`fast-usdc.contract.ts:274`, `test/contract-setup.ts:323` and `:352`). Many more errors get dramatically cleaner messages because their inferred types no longer include the wide `[x: string]: ... | RawGuardPayload | ...` index signatures. Endo's own `lint:types` and `test-d` files remain clean. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…e, ECallableOrMethods When `T = any`, the existing distributive conditionals in `RemoteFunctions<T>`, `PickCallable<T>`, `ECallableOrMethods<T>`, and `ESendOnlyCallableOrMethods<T>` produce a sprawling, unsimplifiable display type that includes `Pick<any, string>` and a union of `(...args: unknown[]) => any` / `(...args: unknown[]) => Promise<any>` crossed with `EMethods<Required<...>>`. TypeScript still resolves property access on the value (since the underlying type is `any`), but error messages and IDE hovers display the wide form, which is unhelpful. Add the standard `0 extends (1 & T)` short-circuit at the top of each helper so `any` propagates through cleanly. This matches the pattern already used in sibling helpers `EAwaitedResult` and `ECallableReturn`. Defensive change: this does not fix any currently-failing call site in agoric-sdk because the failing sites all import `heapVowE` from `@agoric/vow/vat.js`, which has its own fork of these type helpers. A parallel fix should be applied in agoric-sdk's `packages/vow/src/E.js`. This commit improves consumer experience for any code path that goes through `@endo/far`'s `E`. Endo's own `lint:types` and `test-d` files remain clean. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous public type surface from `@endo/eventual-send`'s `exports.d.ts` re-exported the headline helpers (`RemoteFunctions`, `PickCallable`, `LocalRecord`, etc.) but omitted the more granular ones (`ECallable`, `EMethods`, `EGetters`, `ESendOnlyCallable`, `ESendOnlyMethods`, `ESendOnlyCallableOrMethods`, `ECallableOrMethods`). This is fine for direct callers of `E(x)` (whose result type is already wrapped in the public helpers) but it forces downstream forks of the E type machinery — like agoric-sdk's `packages/vow/src/E.js` — to maintain duplicate copies of these helpers locally. Promoting them to the public surface lets vow's fork re-export the byte-equivalent ones via JSDoc and keep only the helpers whose semantics actually differ (those that unwrap Vows via `EUnwrap` rather than just Promises via `Awaited`). No type changes; existing call sites that already imported these via `@endo/eventual-send/src/E.js` continue to work unchanged. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…se() → PromiseLike
Two related improvements to TypeFromPattern that align inference with the
runtime semantics of patterns:
1. TFOptionalTuple uses a homomorphic mapped type with `?`, producing truly
optional tuple elements (`[X?, Y?]`) instead of `[X | undefined, Y |
undefined]`. An earlier comment claimed this was impossible because
`Partial<Tuple>` only works on concrete tuples and conditional types
can't produce optional elements — but TypeScript *does* support
producing optional elements via `{ [K in keyof T]?: ... }` when `T` is
a tuple, regardless of whether `T` is generic. The new form is both
simpler and structurally correct.
This unblocks `M.splitArray(req, opt)` whose inferred type now
matches consumer typedefs like
`TransferPart = [a?, b?, c?, d?]` instead of forcing every caller to
pass exactly N elements with explicit `undefined` placeholders.
2. `M.promise()` now infers as `PromiseLike<T>` rather than `Promise<T>`.
At runtime `M.promise()` checks `passStyleOf === 'promise'`, which is
duck-typed for any thenable, so accepting `PromiseLike<T>` is the
honest type. The previous `Promise<T>` was too narrow — impls
returning `ERef<X>` (= `PromiseLike<X> | X`) failed to match guards
declared with `M.promise()`, even though they would have passed the
runtime check.
Fixes the cascade root in agoric-sdk's
`packages/fast-usdc-contract/src/exos/liquidity-pool.ts`, where
`getPublicTopics` returned `{ poolMetrics: PublicTopic<PoolMetrics> }`
whose `storagePath: ERef<string>` field couldn't satisfy the guard's
inferred `Promise<Passable> | string` shape. Once the
`liquidity-pool` no-overload error clears, the `LiquidityPoolKit` type
becomes well-formed again, which in turn unblocks the 9 `repay`
guarded-mismatch errors in `test/exos/settler.test.ts`.
Also updates the test-d assertions in `types.test-d.ts` to match the
new behavior:
- `M.promise()` → `PromiseLike<any>` (was `Promise<any>`)
- `M.eref(M.string())` → `string | PromiseLike<any>`
- `M.splitArray([...], [...])` → `[X, Y?, Z?]` (was `[X, Y | undefined, Z | undefined]`)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…kit F constraint
Two coordinated changes that let consumers compare exo class kits across
boundaries (e.g. mock vs real impl) without spurious type mismatches.
1. Guarded<M, G>'s G slot is no longer carried via a function-return
field, which is invariant under assignment. Two `Guarded<M, G1>` and
`Guarded<M, G2>` with structurally-compatible `M` would otherwise
fail to assign even when only `G` differs. Now G is split between:
- `__getInterfaceGuard__?: () => InterfaceGuard | undefined` — the
runtime accessor, with a wide return that doesn't constrain G;
- `__interfaceGuard__?(g?: G): void` — a phantom *method* (not a
field). Method shorthand syntax gives the parameter bivariant
treatment under assignment, so the field is structurally compatible
across any G. `GuardedMethods<E>` reads the specific G from this
phantom via `infer`.
2. defineExoClassKit's `F` constraint is widened from
`{ [K in keyof GK]: TypeFromInterfaceGuard<GK[K]> }` to
`Record<FacetName, Methods>`. The narrow form caused TypeScript to
apply contextual typing from the guard-derived shape *into* the impl
method signatures inside `methodsKit` — overwriting the JSDoc/TS
types the implementation author wrote with the guard-derived ones
(e.g. `(args_0: Passable, args_1: Passable) => any` instead of
`(sourceTransfer: TransferPart, amounts: AmountKWR) => void`).
Consumers of the returned kit (e.g. `LiquidityPoolKit['repayer']`)
would then see the guard-derived shape, which doesn't match real
impls or test mocks, breaking subtype checks across module
boundaries.
The trade-off: we lose the compile-time check that impl methods
conform to the guard signature. Runtime conformance is still
enforced by the guard machinery.
Together, these two changes resolve all 15 `repay` guarded-mismatch
errors in agoric-sdk's
`packages/fast-usdc-contract/test/exos/settler.test.ts`, where a mock
`Guarded<{ repay(t, a): void }>` is passed to a function expecting the
`Guarded<{ repay(t, s): void }, InterfaceGuard<...>>` shape derived
from the real impl.
Updates `types-plain-guarded.test-d.ts` to reflect that kit method
params are now driven by the impl's TS types (annotate explicitly to
get param types in kits), not by guard-driven contextual narrowing.
The `makeExo` and `defineExoClass` overloads still narrow params from
the guard.
Repo error count: 117 → 21 (-96, -82%) in `yarn typecheck-quick`.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…array fallback in TFOr/TFAnd
Three small but impactful fixes to TypeFromPattern's handling of
M.or/M.and and the .returns() default:
1. TFOr and TFAnd now accept arrays, not just tuples. The previous
tuple-only form `[infer H, ...infer R]` evaluated to `never` when
the argument was a homogeneous array like `Object.values(Enum)`,
silently collapsing downstream types. Example from agoric-sdk:
M.or(...Object.values(PendingTxStatus))
produced `match:or` with payload `string[]` (not a tuple), so
`TFOr<string[]>` → `never`, and consumers saw
`{ txHash; status: never }` for `matchAndDequeueSettlement`'s
return. The new fallback handles this by returning
`TypeFromPattern<E>` when the input is a bare array.
2. TFKindMap['undefined'] is now `void` rather than `undefined`. At
runtime `M.undefined()` only checks that the value *is*
`undefined`, which a void-returning impl satisfies trivially (JS
returns `undefined` when no value is produced). TypeScript's
distinction is that a void-returning function may actually return
any value (callers must ignore it), while an undefined-returning
function must literally return `undefined`. `void` is the honest
return type — it lets guarded method impls use `): void` without
the guard rejecting them.
Example from agoric-sdk: in fast-usdc-contract's status-manager,
guards like `disbursed: M.call(...).returns(M.undefined())`
previously rejected impls declared `disbursed(...): void` with
"Type 'void' is not assignable to type 'undefined'".
3. Update test-d assertions in both `patterns` and `exo` to reflect
the new `void`-based return types for `M.undefined()`,
`M.call().returns()`, and `M.opt(X)` (which now gives `X | void`
instead of `X | undefined`).
Combined with the agoric-sdk-side guard tightening (tightening
status-manager's method guards to use `M.string<NobleAddress>()`,
`M.string<CaipChainId>()`, `PendingTxShape` for the return of
`matchAndDequeueSettlement`, and `TypedPattern<...>` casts on
AmountKeywordRecord/destination shapes), this clears the
status-manager `zone.exo` no-overload error and its cascade: 19 repo
errors fixed overall.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…cord fix
Two changes to close the remaining application-level "objectMap generic
collapse" errors in agoric-sdk without resorting to casts:
1. @endo/common/object-map.js gains a new `objectExtendEach` helper:
export const objectExtendEach: <O, Ex>(
original: O,
extendFn: <K extends string & keyof O>(value: O[K], key: K) => Ex,
) => { [K in keyof O]: O[K] & Ex }
It is the correlation-preserving form for the common case of "take
an object and add a few fields to every value". The existing
`objectMap<O, R>` collapses R across all keys to a single inferred
type, losing the K-specific correlation; `objectExtendEach` splits
the signature so each returned value is declaratively
`O[K] & Ex` — TypeScript evaluates `O[K]` per key in the return
type expression, so the per-key specific value type survives.
Used in agoric-sdk's `packages/orchestration/tools/contract-tests.ts`
and `packages/fast-usdc-deploy/src/utils/deploy-config.js` to
replace `objectMap(ci, v => ({ ...v, chainId: ... }))` patterns
that previously needed `as unknown as` casts.
2. `TFSplitRecord` in @endo/patterns now treats an empty-record rest
pattern `M.splitRecord(req, opt, {})` the same as no rest pattern.
The `{}` is canonically used to mean "refuse unsupported options"
at runtime, but TypeFromPattern used to emit a literal
`[key: string]: {}` index signature for it, which:
- disallows `undefined` values for known keys (since `{}` excludes
`undefined`, and the index signature applies to all keys), and
- pollutes consumers with a wildcard that breaks excess-property
checking on the enclosing object.
The fix checks `[keyof Rest] extends [never]` and skips emitting
the index signature in that case. Non-empty rest patterns are
unaffected.
Fixes the `contract-control.contract.js:199` error where
`self.start({ installation, issuers, privateArgsOverrides })`
couldn't pass `issuers: IssuerKeywordRecord | undefined` because
the contextual type had `[x: string]: {}` forbidding undefined.
Endo's own `lint:types` remains clean (modulo the 2 pre-existing
`@ts-expect-error` directives in `packages/exo/test/heap-classes.test.js`).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to master, this PR will be updated. # Releases ## @endo/ocapn@1.0.0 ### Major Changes - [#3183](#3183) [`279c0c4`](279c0c4) Thanks [@kumavis](https://github.com/kumavis)! - Initial public release of `@endo/ocapn`. The package is no longer private and is now published to npm. Tested against the python test suite from 2026-01-06 <https://github.com/ocapn/ocapn-test-suite/commits/f0273f21c5ee05a28785b51c231535124f28bca9> ### Minor Changes - [#3172](#3172) [`6405b36`](6405b36) Thanks [@turadg](https://github.com/turadg)! - Parameterize CapTP slot types and improve TypeScript 6 conformance across the OCapN client surface. Compile-time type changes only; no runtime behavior changes. ### Patch Changes - Updated dependencies \[[`f65b000`](f65b000), [`d1d9625`](d1d9625), [`88bc2b9`](88bc2b9), [`e619205`](e619205), [`43165e5`](43165e5), [`6ada52b`](6ada52b)]: - @endo/eventual-send@1.5.0 - @endo/promise-kit@1.2.1 - @endo/pass-style@1.8.0 - @endo/marshal@1.9.1 - @endo/harden@1.1.0 - @endo/nat@5.2.0 ## ses@2.0.0 ### Major Changes - [#3153](#3153) [`e619205`](e619205) Thanks [@erights](https://github.com/erights)! - # Plug NaN Side-channel The JavaScript language can leak the bit encoding of a NaN via shared TypedArray views of an common ArrayBuffer. Although the JavaScript language has only one NaN value, the underlying IEEE 754 double-precision floating-point representation has many different bit patterns that represent NaN. This can be exploited as a side-channel to leak information. This actually happens on some platforms such as v8. @ChALkeR explains at <tc39/ecma262#758 (comment)> that the behavior of this side-channel on v8. At <https://junk.rray.org/poc/nani.html> he demonstrates it, and it indeed even worse than I expected. To plug this side-channel, we make two coordinated changes. - We stop listing the `Float*Array` constructors as universal globals. This prevents them from being implicitly endowed to created compartments, because they are not harmless. However, we still keep them on the start compartment (the original global), consider them intrinsics, and still repair and harden them on `lockdown()`. Thus, they can be explicitly endowed to child compartments at the price of enabling code in that compartment to read the side-channel. - On `lockdown()`, we repair the `DataView.prototype.setFloat*` methods so that they only write canonical NaNs into the underlying ArrayBuffer. The `@endo.marshal` package's `encodePassable` encodings need to obtain the bit representation of floating point values. It had used `Float64Array` for that. However, sometimes the `@endo/marshal` package is evaluated in a created compartment that would now lack that constructor. (This reevaluation typically occurs when bundling bundles in that package.) So instead, `encodePassable` now uses the `DataView` methods which are now safe. ### Minor Changes - [#3129](#3129) [`a675d8e`](a675d8e) Thanks [@erights](https://github.com/erights)! - `overrideTaming: 'moderate'` includes `overrideTaming: 'min'`. Previously `overrideTaming: 'min'` correctly enabled `Iterator.prototype.constructor` to be overridden by assignment, but due to an oversight, `overrideTaming: 'moderate'` did not. Now it does. To make such mistakes less likely, this PR also adopts a style where all records within larger enablements triple-dot the corresponding record from a smaller enablement, if present. ## @endo/bundle-source@4.3.0 ### Minor Changes - [#3180](#3180) [`7f7ae8e`](7f7ae8e) Thanks [@turadg](https://github.com/turadg)! - `BundleCache.load()` is now generic on the `format` option: - Omitted (default) → `Promise<BundleSourceResult<'endoZipBase64'>>` - Literal format → `Promise<BundleSourceResult<format>>` - Runtime-typed `ModuleFormat` → `Promise<BundleSourceResult<ModuleFormat>>` Previously `load()` returned `Promise<unknown>`, requiring callers to assert the bundle shape. ### Patch Changes - Updated dependencies \[[`154102b`](154102b), [`2b674ca`](2b674ca), [`d1d9625`](d1d9625), [`b4820dc`](b4820dc), [`acbacba`](acbacba), [`cdb6eae`](cdb6eae), [`6ada52b`](6ada52b), [`6ad084a`](6ad084a), [`1cd1246`](1cd1246)]: - @endo/compartment-mapper@2.1.0 - @endo/promise-kit@1.2.1 - @endo/harden@1.1.0 ## @endo/common@1.4.0 ### Minor Changes - [#3172](#3172) [`98c89b7`](98c89b7) Thanks [@turadg](https://github.com/turadg)! - Add `objectExtendEach` helper for merging a sequence of objects into an accumulator, with precise TypeScript inference of the resulting intersection type. ### Patch Changes - Updated dependencies \[[`f65b000`](f65b000), [`d1d9625`](d1d9625)]: - @endo/eventual-send@1.5.0 - @endo/promise-kit@1.2.1 - @endo/errors@1.3.1 - @endo/harden@1.1.0 ## @endo/compartment-mapper@2.1.0 ### Minor Changes - [#3132](#3132) [`b4820dc`](b4820dc) Thanks [@boneskull](https://github.com/boneskull)! - Expose `_redundantPreloadHook` option in `captureFromMap()`, which will be called for each item in the `_preload` array that was already indirectly loaded via the entry `Compartment`. Fixes a bug in the type of `_preload` option, which now allows for mixed arrays. Fixes a bug in the preloader, which was not exhaustively checking if a non-entry module was already loaded via the entry `Compartment`. - [#3048](#3048) [`6ad084a`](6ad084a) Thanks [@kriskowal](https://github.com/kriskowal)! - Add support for Node.js subpath pattern replacement in `package.json` `exports` and `imports` fields. Patterns like `"./features/*.js": "./src/features/*.js"` and `"#internal/*.js": "./lib/*.js"` are now resolved at link time using prefix/suffix string matching with specificity ordering. Null-target patterns exclude matching specifiers. Conditional pattern values are resolved through the standard condition-matching rules. Patterns are expanded to concrete module entries during archiving. ### Patch Changes - [#3111](#3111) [`154102b`](154102b) Thanks [@boneskull](https://github.com/boneskull)! - Fix type of `PackageDataHook.packageData` which now correctly allows `$root$` as a key. - [#3182](#3182) [`2b674ca`](2b674ca) Thanks [@kriskowal](https://github.com/kriskowal)! - Cull underscore-prefixed internal properties (like `__createdBy`) from serialized compartment maps in archives. The compartment map validator now also ignores underscore-prefixed properties when checking for extraneous fields. - [#3173](#3173) [`acbacba`](acbacba) Thanks [@boneskull](https://github.com/boneskull)! - Fixes potential issue wherein a canonical name may be computed incorrectly. Includes performance improvements. - [#3157](#3157) [`cdb6eae`](cdb6eae) Thanks [@boneskull](https://github.com/boneskull)! - Dramatically improve performance of canonical name (shortest path) computation in `mapNodeModules()`. - [#3127](#3127) [`6ada52b`](6ada52b) Thanks [@turadg](https://github.com/turadg)! - Remove stale runtime dependencies from package manifests. - [#3115](#3115) [`1cd1246`](1cd1246) Thanks [@boneskull](https://github.com/boneskull)! - Remove unused "error" `ModuleSourceHookModuleSource` type. - Updated dependencies \[[`e619205`](e619205), [`6ada52b`](6ada52b), [`a675d8e`](a675d8e)]: - ses@2.0.0 - @endo/module-source@1.4.1 ## @endo/eventual-send@1.5.0 ### Minor Changes - [#3172](#3172) [`f65b000`](f65b000) Thanks [@turadg](https://github.com/turadg)! - Improve `E()` type inference and publicly export method-projection helpers. - `RemoteFunctions`, `PickCallable`, and `ECallableOrMethods` now short-circuit on `any`, preventing `E(anyValue)` from collapsing to an unusable type. - `EMethods`, `EGetters`, and related helpers are now part of the public type surface, so downstream packages can name the projected shapes `E()` produces. Compile-time type changes only; no runtime behavior changes. ### Patch Changes - Updated dependencies \[]: - @endo/harden@1.1.0 ## @endo/exo@1.7.0 ### Minor Changes - [#3172](#3172) [`88bc2b9`](88bc2b9) Thanks [@turadg](https://github.com/turadg)! - Improve TypeScript inference for patterns, exo, and pass-style. These are compile-time type changes only; no runtime behavior changes. - **pass-style**: `CopyArray<T>` is now `readonly T[]` so readonly tuples (e.g. `readonly ['ibc']`) satisfy `Passable`. Backward-compatible because `T[]` still extends `readonly T[]`. - **patterns**: `M.remotable()` defaults to `any` (matching `M.promise()`), so unparameterized remotables are assignable to concrete remotable typedefs. The parameterized form `M.remotable<typeof SomeInterfaceGuard>()` still yields precise inference. - **patterns**: `TFRemotable` returns `any` (not `Payload`) for non-`InterfaceGuard` arguments. - **patterns**: `TFOr` handles array-of-patterns and falls back through `TFAnd`; `M.undefined()` maps to `void`. - **patterns**: `TFOptionalTuple` emits truly optional elements; `M.promise()` maps to `PromiseLike`. - **patterns**: `TFSplitRecord` handles the empty-rest case correctly. - **patterns**: `TFRestArgs` unwraps array patterns. - **patterns**: `TypeFromArgGuard` discriminates by `toStringTag`, not structural shape. - **patterns**: `MatcherOf` payload is preserved through `InterfaceGuard`. - **patterns**: new `CastedPattern<T>` for unchecked type assertions in pattern position. - **exo**: `defineExoClass`, `defineExoClassKit`, and `makeExo` no longer intersect facet constraints with `& Methods`. The previous constraint collapsed specific facet keys into the `string | number | symbol` index signature, making `FilteredKeys` return `never` and erasing facet method inference (`Pick<X, never> = {}`). - **exo**: `Guarded<M, G>` is now structurally compatible across `G`, and the kit `F` constraint is widened. - **exo**: `defineExoClassKit` preserves facet inference when no guard is supplied. TypeScript consumers that were working around the previous inference gaps with casts may be able to remove those casts. Downstream code that depended on the narrower `CopyArray<T> = T[]` or the previous `M.remotable()` default may need minor adjustments. - [#3133](#3133) [`9111b4e`](9111b4e) Thanks [@turadg](https://github.com/turadg)! - feat: infer TypeScript types from pattern guards - `TypeFromPattern<P>` — infer static types from any pattern matcher - `TypeFromMethodGuard<G>` — infer function signatures from `M.call()` / `M.callWhen()` guards - `TypeFromInterfaceGuard<G>` — infer method records from interface guard definitions - `M.remotable<typeof Guard>()` — facet-isolated return types in exo kits - `M.infer<typeof pattern>` — namespace shorthand analogous to `z.infer` - `matches` and `mustMatch` now narrow the specimen type via type predicates - `makeExo`, `defineExoClass`, and `defineExoClassKit` enforce method signatures against guards at compile time These are compile-time type changes only; there are no runtime behavioral changes. Existing TypeScript consumers may see new type errors where method signatures diverge from their guards. ### Patch Changes - Updated dependencies \[[`8195a5a`](8195a5a), [`98c89b7`](98c89b7), [`f65b000`](f65b000), [`88bc2b9`](88bc2b9), [`9111b4e`](9111b4e), [`43165e5`](43165e5), [`df84eea`](df84eea), [`6ada52b`](6ada52b)]: - @endo/patterns@1.9.0 - @endo/common@1.4.0 - @endo/eventual-send@1.5.0 - @endo/pass-style@1.8.0 - @endo/errors@1.3.1 - @endo/far@1.1.14 - @endo/harden@1.1.0 ## @endo/pass-style@1.8.0 ### Minor Changes - [#3172](#3172) [`88bc2b9`](88bc2b9) Thanks [@turadg](https://github.com/turadg)! - Improve TypeScript inference for patterns, exo, and pass-style. These are compile-time type changes only; no runtime behavior changes. - **pass-style**: `CopyArray<T>` is now `readonly T[]` so readonly tuples (e.g. `readonly ['ibc']`) satisfy `Passable`. Backward-compatible because `T[]` still extends `readonly T[]`. - **patterns**: `M.remotable()` defaults to `any` (matching `M.promise()`), so unparameterized remotables are assignable to concrete remotable typedefs. The parameterized form `M.remotable<typeof SomeInterfaceGuard>()` still yields precise inference. - **patterns**: `TFRemotable` returns `any` (not `Payload`) for non-`InterfaceGuard` arguments. - **patterns**: `TFOr` handles array-of-patterns and falls back through `TFAnd`; `M.undefined()` maps to `void`. - **patterns**: `TFOptionalTuple` emits truly optional elements; `M.promise()` maps to `PromiseLike`. - **patterns**: `TFSplitRecord` handles the empty-rest case correctly. - **patterns**: `TFRestArgs` unwraps array patterns. - **patterns**: `TypeFromArgGuard` discriminates by `toStringTag`, not structural shape. - **patterns**: `MatcherOf` payload is preserved through `InterfaceGuard`. - **patterns**: new `CastedPattern<T>` for unchecked type assertions in pattern position. - **exo**: `defineExoClass`, `defineExoClassKit`, and `makeExo` no longer intersect facet constraints with `& Methods`. The previous constraint collapsed specific facet keys into the `string | number | symbol` index signature, making `FilteredKeys` return `never` and erasing facet method inference (`Pick<X, never> = {}`). - **exo**: `Guarded<M, G>` is now structurally compatible across `G`, and the kit `F` constraint is widened. - **exo**: `defineExoClassKit` preserves facet inference when no guard is supplied. TypeScript consumers that were working around the previous inference gaps with casts may be able to remove those casts. Downstream code that depended on the narrower `CopyArray<T> = T[]` or the previous `M.remotable()` default may need minor adjustments. - [#3184](#3184) [`43165e5`](43165e5) Thanks [@turadg](https://github.com/turadg)! - Unblock TypeScript declaration emit in downstream packages that structurally expose `PassStyled`/`Container` types. Compile-time type changes only; no runtime behavior changes. - `PASS_STYLE` is now typed as the string-literal `'Symbol(passStyle)'` rather than `unique symbol`. The runtime value is unchanged (still `Symbol.for('passStyle')`), and computed-key indexing like `obj[PASS_STYLE]` continues to work because JS computed keys accept any value. This removes TS4023 / TS9006 errors in consumers whose inferred types structurally contain `[PASS_STYLE]` (via `PassStyled`, `ExtractStyle`, object spread of a `PassStyled`, etc.). A `unique symbol` is only nameable via its original declaration module, which consumers have no reason to import; a string-literal type has no such nameability requirement. - `CopyArrayInterface`, `CopyRecordInterface`, and `CopyTaggedInterface` are now exported, so downstream `.d.ts` emit can name them when they appear through structural expansion of `Passable`/`Container`. - The `PassStyleOf` array overload is widened from `(p: any[]) => 'copyArray'` to `(p: readonly any[]) => 'copyArray'`, so `as const` tuples and `readonly T[]` values classify as `'copyArray'`. This aligns the classifier with `CopyArray<T>`, which is already `readonly T[]`. Backward-compatible because `T[]` still extends `readonly T[]`. Obviates the `@endo/pass-style` patch that agoric-sdk has been carrying in `.yarn/patches/`. TypeScript consumers that relied on `typeof PASS_STYLE` being `unique symbol` (e.g. annotating a value as `symbol` from `PASS_STYLE`) will need minor adjustments — widen the annotation to `symbol | string`, or cast via `unknown`. ### Patch Changes - [#3127](#3127) [`6ada52b`](6ada52b) Thanks [@turadg](https://github.com/turadg)! - Remove stale runtime dependencies from package manifests. - Updated dependencies \[[`98c89b7`](98c89b7), [`f65b000`](f65b000), [`d1d9625`](d1d9625)]: - @endo/common@1.4.0 - @endo/eventual-send@1.5.0 - @endo/promise-kit@1.2.1 - @endo/errors@1.3.1 - @endo/harden@1.1.0 ## @endo/patterns@1.9.0 ### Minor Changes - [#3067](#3067) [`8195a5a`](8195a5a) Thanks [@gibson042](https://github.com/gibson042)! - - Updates `containerHasSplit` to consider copyArray elements in forward order, better aligning with intuition. - [#3172](#3172) [`88bc2b9`](88bc2b9) Thanks [@turadg](https://github.com/turadg)! - Improve TypeScript inference for patterns, exo, and pass-style. These are compile-time type changes only; no runtime behavior changes. - **pass-style**: `CopyArray<T>` is now `readonly T[]` so readonly tuples (e.g. `readonly ['ibc']`) satisfy `Passable`. Backward-compatible because `T[]` still extends `readonly T[]`. - **patterns**: `M.remotable()` defaults to `any` (matching `M.promise()`), so unparameterized remotables are assignable to concrete remotable typedefs. The parameterized form `M.remotable<typeof SomeInterfaceGuard>()` still yields precise inference. - **patterns**: `TFRemotable` returns `any` (not `Payload`) for non-`InterfaceGuard` arguments. - **patterns**: `TFOr` handles array-of-patterns and falls back through `TFAnd`; `M.undefined()` maps to `void`. - **patterns**: `TFOptionalTuple` emits truly optional elements; `M.promise()` maps to `PromiseLike`. - **patterns**: `TFSplitRecord` handles the empty-rest case correctly. - **patterns**: `TFRestArgs` unwraps array patterns. - **patterns**: `TypeFromArgGuard` discriminates by `toStringTag`, not structural shape. - **patterns**: `MatcherOf` payload is preserved through `InterfaceGuard`. - **patterns**: new `CastedPattern<T>` for unchecked type assertions in pattern position. - **exo**: `defineExoClass`, `defineExoClassKit`, and `makeExo` no longer intersect facet constraints with `& Methods`. The previous constraint collapsed specific facet keys into the `string | number | symbol` index signature, making `FilteredKeys` return `never` and erasing facet method inference (`Pick<X, never> = {}`). - **exo**: `Guarded<M, G>` is now structurally compatible across `G`, and the kit `F` constraint is widened. - **exo**: `defineExoClassKit` preserves facet inference when no guard is supplied. TypeScript consumers that were working around the previous inference gaps with casts may be able to remove those casts. Downstream code that depended on the narrower `CopyArray<T> = T[]` or the previous `M.remotable()` default may need minor adjustments. - [#3133](#3133) [`9111b4e`](9111b4e) Thanks [@turadg](https://github.com/turadg)! - feat: infer TypeScript types from pattern guards - `TypeFromPattern<P>` — infer static types from any pattern matcher - `TypeFromMethodGuard<G>` — infer function signatures from `M.call()` / `M.callWhen()` guards - `TypeFromInterfaceGuard<G>` — infer method records from interface guard definitions - `M.remotable<typeof Guard>()` — facet-isolated return types in exo kits - `M.infer<typeof pattern>` — namespace shorthand analogous to `z.infer` - `matches` and `mustMatch` now narrow the specimen type via type predicates - `makeExo`, `defineExoClass`, and `defineExoClassKit` enforce method signatures against guards at compile time These are compile-time type changes only; there are no runtime behavioral changes. Existing TypeScript consumers may see new type errors where method signatures diverge from their guards. - [#3133](#3133) [`df84eea`](df84eea) Thanks [@turadg](https://github.com/turadg)! - Add optional `label` parameter to `M.promise()`, aligning its signature with `M.remotable(label?)`. When a label is provided, runtime error messages include it for diagnostics (e.g., "Must be a promise Foo, not remotable"). ### Patch Changes - [#3127](#3127) [`6ada52b`](6ada52b) Thanks [@turadg](https://github.com/turadg)! - Remove stale runtime dependencies from package manifests. - Updated dependencies \[[`98c89b7`](98c89b7), [`f65b000`](f65b000), [`88bc2b9`](88bc2b9), [`e619205`](e619205), [`43165e5`](43165e5), [`6ada52b`](6ada52b)]: - @endo/common@1.4.0 - @endo/eventual-send@1.5.0 - @endo/pass-style@1.8.0 - @endo/marshal@1.9.1 - @endo/errors@1.3.1 - @endo/harden@1.1.0 ## @endo/check-bundle@1.1.1 ### Patch Changes - [#3182](#3182) [`2b674ca`](2b674ca) Thanks [@kriskowal](https://github.com/kriskowal)! - Cull underscore-prefixed internal properties (like `__createdBy`) from serialized compartment maps in archives. The compartment map validator now also ignores underscore-prefixed properties when checking for extraneous fields. - Updated dependencies \[[`154102b`](154102b), [`2b674ca`](2b674ca), [`b4820dc`](b4820dc), [`acbacba`](acbacba), [`cdb6eae`](cdb6eae), [`6ada52b`](6ada52b), [`6ad084a`](6ad084a), [`1cd1246`](1cd1246)]: - @endo/compartment-mapper@2.1.0 - @endo/errors@1.3.1 - @endo/harden@1.1.0 ## @endo/errors@1.3.1 ### Patch Changes - Updated dependencies \[[`e619205`](e619205), [`a675d8e`](a675d8e)]: - ses@2.0.0 - @endo/harden@1.1.0 ## @endo/import-bundle@1.6.1 ### Patch Changes - Updated dependencies \[[`154102b`](154102b), [`2b674ca`](2b674ca), [`b4820dc`](b4820dc), [`acbacba`](acbacba), [`e619205`](e619205), [`cdb6eae`](cdb6eae), [`6ada52b`](6ada52b), [`6ad084a`](6ad084a), [`1cd1246`](1cd1246), [`a675d8e`](a675d8e)]: - @endo/compartment-mapper@2.1.0 - ses@2.0.0 - @endo/errors@1.3.1 - @endo/harden@1.1.0 ## @endo/lockdown@1.0.19 ### Patch Changes - Updated dependencies \[[`e619205`](e619205), [`a675d8e`](a675d8e)]: - ses@2.0.0 ## @endo/lp32@1.2.1 ### Patch Changes - [#3127](#3127) [`6ada52b`](6ada52b) Thanks [@turadg](https://github.com/turadg)! - Remove stale runtime dependencies from package manifests. - Updated dependencies \[]: - @endo/errors@1.3.1 - @endo/harden@1.1.0 - @endo/stream@1.3.1 ## @endo/marshal@1.9.1 ### Patch Changes - [#3153](#3153) [`e619205`](e619205) Thanks [@erights](https://github.com/erights)! - # Plug NaN Side-channel The JavaScript language can leak the bit encoding of a NaN via shared TypedArray views of an common ArrayBuffer. Although the JavaScript language has only one NaN value, the underlying IEEE 754 double-precision floating-point representation has many different bit patterns that represent NaN. This can be exploited as a side-channel to leak information. This actually happens on some platforms such as v8. @ChALkeR explains at <tc39/ecma262#758 (comment)> that the behavior of this side-channel on v8. At <https://junk.rray.org/poc/nani.html> he demonstrates it, and it indeed even worse than I expected. To plug this side-channel, we make two coordinated changes. - We stop listing the `Float*Array` constructors as universal globals. This prevents them from being implicitly endowed to created compartments, because they are not harmless. However, we still keep them on the start compartment (the original global), consider them intrinsics, and still repair and harden them on `lockdown()`. Thus, they can be explicitly endowed to child compartments at the price of enabling code in that compartment to read the side-channel. - On `lockdown()`, we repair the `DataView.prototype.setFloat*` methods so that they only write canonical NaNs into the underlying ArrayBuffer. The `@endo.marshal` package's `encodePassable` encodings need to obtain the bit representation of floating point values. It had used `Float64Array` for that. However, sometimes the `@endo/marshal` package is evaluated in a created compartment that would now lack that constructor. (This reevaluation typically occurs when bundling bundles in that package.) So instead, `encodePassable` now uses the `DataView` methods which are now safe. - [#3127](#3127) [`6ada52b`](6ada52b) Thanks [@turadg](https://github.com/turadg)! - Remove stale runtime dependencies from package manifests. - Updated dependencies \[[`98c89b7`](98c89b7), [`f65b000`](f65b000), [`88bc2b9`](88bc2b9), [`43165e5`](43165e5), [`6ada52b`](6ada52b)]: - @endo/common@1.4.0 - @endo/eventual-send@1.5.0 - @endo/pass-style@1.8.0 - @endo/errors@1.3.1 - @endo/harden@1.1.0 - @endo/nat@5.2.0 ## @endo/memoize@1.2.1 ### Patch Changes - [#3107](#3107) [`05cdb5f`](05cdb5f) Thanks [@erights](https://github.com/erights)! - `@endo/memoize` no longer depends on `ses`, just `@endo/harden` - Updated dependencies \[]: - @endo/harden@1.1.0 ## @endo/module-source@1.4.1 ### Patch Changes - [#3127](#3127) [`6ada52b`](6ada52b) Thanks [@turadg](https://github.com/turadg)! - Remove stale runtime dependencies from package manifests. - Updated dependencies \[[`e619205`](e619205), [`a675d8e`](a675d8e)]: - ses@2.0.0 ## @endo/netstring@1.1.1 ### Patch Changes - [#3127](#3127) [`6ada52b`](6ada52b) Thanks [@turadg](https://github.com/turadg)! - Remove stale runtime dependencies from package manifests. - Updated dependencies \[[`d1d9625`](d1d9625)]: - @endo/promise-kit@1.2.1 - @endo/harden@1.1.0 - @endo/stream@1.3.1 ## @endo/promise-kit@1.2.1 ### Patch Changes - [#3108](#3108) [`d1d9625`](d1d9625) Thanks [@erights](https://github.com/erights)! - `@endo/promise-kit` no longer depends on `ses`, just `@endo/harden` - Updated dependencies \[]: - @endo/harden@1.1.0 ## @endo/ses-ava@1.4.1 ### Patch Changes - Updated dependencies \[[`e619205`](e619205), [`a675d8e`](a675d8e)]: - ses@2.0.0 - @endo/harden@1.1.0 ## @endo/stream@1.3.1 ### Patch Changes - Updated dependencies \[[`f65b000`](f65b000), [`d1d9625`](d1d9625), [`e619205`](e619205), [`a675d8e`](a675d8e)]: - @endo/eventual-send@1.5.0 - @endo/promise-kit@1.2.1 - ses@2.0.0 - @endo/harden@1.1.0 ## @endo/stream-node@1.2.1 ### Patch Changes - [#3127](#3127) [`6ada52b`](6ada52b) Thanks [@turadg](https://github.com/turadg)! - Remove stale runtime dependencies from package manifests. - Updated dependencies \[]: - @endo/errors@1.3.1 - @endo/harden@1.1.0 - @endo/stream@1.3.1 ## @endo/daemon@2.5.3 ### Patch Changes - Updated dependencies \[[`8195a5a`](8195a5a), [`154102b`](154102b), [`2b674ca`](2b674ca), [`f65b000`](f65b000), [`d1d9625`](d1d9625), [`b4820dc`](b4820dc), [`88bc2b9`](88bc2b9), [`9111b4e`](9111b4e), [`acbacba`](acbacba), [`e619205`](e619205), [`df84eea`](df84eea), [`cdb6eae`](cdb6eae), [`6ada52b`](6ada52b), [`6ad084a`](6ad084a), [`1cd1246`](1cd1246), [`a675d8e`](a675d8e)]: - @endo/patterns@1.9.0 - @endo/compartment-mapper@2.1.0 - @endo/eventual-send@1.5.0 - @endo/promise-kit@1.2.1 - @endo/exo@1.7.0 - ses@2.0.0 - @endo/marshal@1.9.1 - @endo/netstring@1.1.1 - @endo/stream-node@1.2.1 - @endo/captp@4.5.0 - @endo/errors@1.3.1 - @endo/far@1.1.14 - @endo/harden@1.1.0 - @endo/import-bundle@1.6.1 - @endo/stream@1.3.1 ## @endo/stream-types-test@1.0.19 ### Patch Changes - Updated dependencies \[[`e619205`](e619205), [`a675d8e`](a675d8e)]: - ses@2.0.0 - @endo/nat@5.2.0 - @endo/stream@1.3.1 ## @endo/test262-runner@0.1.50 ### Patch Changes - Updated dependencies \[[`154102b`](154102b), [`2b674ca`](2b674ca), [`b4820dc`](b4820dc), [`acbacba`](acbacba), [`e619205`](e619205), [`cdb6eae`](cdb6eae), [`6ada52b`](6ada52b), [`6ad084a`](6ad084a), [`1cd1246`](1cd1246), [`a675d8e`](a675d8e)]: - @endo/compartment-mapper@2.1.0 - ses@2.0.0
Refs: #3171
Description
Improves TypeScript inference across
@endo/exo,@endo/patterns,@endo/pass-style,@endo/eventual-send, and@endo/ocapn, discovered while integrating Endo with agoric-sdk under TypeScript 6. The changes are almost entirely type-level — no runtime behavior changes — and each commit is a self-contained fix so they can be reviewed (and reverted) independently.Notable fixes:
CopyArray<T>is nowreadonly T[], so readonly tuples (e.g.readonly ["ibc"]) satisfyPassable. Backward-compatible sinceT[]still extendsreadonly T[].M.remotable()defaults toany(matchingM.promise()) so unparameterized remotables are assignable to concrete remotable typedefs downstream.TFRemotable,TFOr,TFOptionalTuple,TFSplitRecord,TFRestArgs,TypeFromArgGuard, andMatcherOfpropagation throughInterfaceGuardall tightened. AddsCastedPattern<T>for unchecked type assertions andobjectExtendEachhelper.defineExoClass,defineExoClassKit, andmakeExono longer intersect facet constraints with& Methods, which was collapsing specific facet keys into the index signature and erasing facet method inference (Pick<X, never> = {}).Guarded<M, G>is now structurally compatible acrossG.anyinRemoteFunctions,PickCallable, andECallableOrMethods. Publicly exportsEMethods/EGettersand friends.The most critical files to review are the type definitions under
packages/patterns/src/types.d.ts,packages/exo/src/types.d.ts,packages/pass-style/src/types.js, andpackages/eventual-send/src/types.d.ts.Security Considerations
None. Type-only changes; no new authority, no change to mutually-suspicious component boundaries.
Scaling Considerations
None. No runtime changes.
Documentation Considerations
No user-facing API changes. Downstream consumers that were working around the inference bugs with casts may be able to remove those casts, but existing casts remain valid.
Testing Considerations
Covered by existing unit tests plus the root
tsctype-check, which this PR makes pass cleanly under TypeScript 6. The real-world validation is successful integration with agoric-sdk, where these inference gaps were originally surfaced.Compatibility Considerations
Backward-compatible at the value level. At the type level, a few overly-narrow types were widened (e.g.
CopyArray<T>toreadonly T[],M.remotable()default toany); downstream code that depended on the previous narrower shape may need minor adjustments, but the common case is that previously-broken inference now works without changes.Upgrade Considerations
None — no persistent state, no migrations, no breaking runtime changes.