test(evm): verify static descriptor bindings before use (#2427)
Verify the static EVM descriptor operation, CLI command, factory and input/output schema bindings with compiler-backed negative assignability checks. Prove SDK duplicate registration and invalid target input fail before provider invocation, preserving named bindings and separate lazy owners. Validated complete supported single-thread typecheck, focused12 SDK/composition cases plus a targeted2-case provider-spy rerun, static checks, checked pre-push, manual review and all exact-head CI checks. Production behavior/defaults and provider identity remain unchanged. Dynamic third-party module admission and ABI negotiation remain deferred. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
parent
ee87111009
commit
4f4f6cd7b5
|
|
@ -179,3 +179,40 @@ interface question through an explicitly selected precise view, retain full
|
|||
bytecode/original Evidence under the existing lifetime, and preserve complete
|
||||
defaults. Discover support before sending new options to an older server. Include
|
||||
discovery, follow-up, failures and retries in the complete task cost.
|
||||
|
||||
## Static contract admission verification
|
||||
|
||||
The follow-up from main `c462a4ebd3a10e59253bcf38353a0914b176738b` tests the
|
||||
pilot's actual static boundary, without adding a dynamic module registry or
|
||||
changing production code. Complete-project TypeScript checking now verifies
|
||||
that the inferred descriptor type rejects a WASM operation contract, a WASM
|
||||
CLI command, a WASM service factory, an unrelated input schema and an unrelated
|
||||
output schema. These are negative assignability assertions: their guarantee
|
||||
comes from the compiler, not from executing an incompatible descriptor.
|
||||
The existing named contract and CLI identity are also checked at runtime;
|
||||
separate factory calls produce distinct service/provider owners. Invalid target
|
||||
input remains an application error without launching a worker.
|
||||
|
||||
The actual public in-memory MCP SDK test additionally spies on the provider:
|
||||
duplicate EVM registration throws the named `already registered` error before
|
||||
any provider call. Converted input/output JSON Schemas validate before the
|
||||
first valid operation. Invalid client arguments are rejected before execution;
|
||||
incompatible provider reply data is validated **after** execution and is not
|
||||
presented as a pre-use contract guarantee. Existing healthy-peer, cancellation,
|
||||
retained summary/default and cleanup-retry scenarios remain covered.
|
||||
|
||||
Full project typecheck passes with the installed compiler's supported single
|
||||
thread scheduling; it includes all source/test files. The two exact focused
|
||||
files pass 12 runtime cases. The resource scopes sample 2,057,961,472 bytes for
|
||||
typecheck and 2,426,290,176 bytes for required build plus focused tests, below the
|
||||
unchanged 3 GiB early-stop/4 GiB hard ceiling. Final transient-cgroup counters
|
||||
remain unknown. No new real-provider or performance improvement is claimed by
|
||||
these compile/SDK checks; the earlier pinned real EVMole/WABT/lifecycle evidence
|
||||
retains its original scope and source identity.
|
||||
|
||||
This proves the one statically composed descriptor's named/schema/factory
|
||||
compatibility and duplicate-registration guard. It does not implement runtime
|
||||
admission of arbitrary JavaScript descriptors, third-party plugin ABI/version
|
||||
negotiation or a general plugin SDK. Those remain deferred by the original plan.
|
||||
Existing callers, full defaults, provider inputs, owned lifetimes and output
|
||||
identities are unchanged.
|
||||
|
|
|
|||
|
|
@ -28,8 +28,11 @@ import {
|
|||
|
||||
it("advertises exact valid schemas and records inline EVM interface evidence without a binary target", async () => {
|
||||
const value = evmInterfaceFixture();
|
||||
const inspect = vi.fn(() =>
|
||||
Promise.resolve(ok(evmInterfaceExecution(value))),
|
||||
);
|
||||
const service = new EvmInterfaceService({
|
||||
inspect: () => Promise.resolve(ok(evmInterfaceExecution(value))),
|
||||
inspect,
|
||||
});
|
||||
const session = createTestBinarySession(() => {
|
||||
throw new Error("deep provider must not start");
|
||||
|
|
@ -71,6 +74,7 @@ it("advertises exact valid schemas and records inline EVM interface evidence wit
|
|||
expect(() => registerEvmTools(server, service, silentLogger)).toThrow(
|
||||
/inspect_evm_interface.*already registered/,
|
||||
);
|
||||
expect(inspect).not.toHaveBeenCalled();
|
||||
expect(
|
||||
(await client.listTools()).tools.filter(
|
||||
(tool) => tool.name === "inspect_evm_interface",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
import { expect, expectTypeOf, it, vi } from "vitest";
|
||||
import { z } from "zod";
|
||||
import { evmInterfaceModule } from "../../src/composition/evm.js";
|
||||
import { wasmArtifactModule } from "../../src/composition/wasm.js";
|
||||
import { CLI_COMMANDS } from "../../src/cliCommandNames.js";
|
||||
import { INSPECT_EVM_INTERFACE_CONTRACT } from "../../src/contracts/evm/evmToolContracts.js";
|
||||
|
||||
it("rejects incompatible static bindings through the complete project typecheck", () => {
|
||||
// expect-type's negative assertions are checked by tsc, not the runtime
|
||||
// test runner. No malformed descriptor is registered or executed here.
|
||||
expectTypeOf({
|
||||
...evmInterfaceModule,
|
||||
contract: wasmArtifactModule.contract,
|
||||
}).not.toExtend<typeof evmInterfaceModule>();
|
||||
expectTypeOf({
|
||||
...evmInterfaceModule,
|
||||
cliCommand: CLI_COMMANDS.inspectWasmArtifact,
|
||||
}).not.toExtend<typeof evmInterfaceModule>();
|
||||
expectTypeOf({
|
||||
...evmInterfaceModule,
|
||||
create: wasmArtifactModule.create,
|
||||
}).not.toExtend<typeof evmInterfaceModule>();
|
||||
expectTypeOf({
|
||||
...evmInterfaceModule,
|
||||
contract: {
|
||||
...evmInterfaceModule.contract,
|
||||
inputSchema: z.strictObject({ unrelated: z.string() }),
|
||||
},
|
||||
}).not.toExtend<typeof evmInterfaceModule>();
|
||||
expectTypeOf({
|
||||
...evmInterfaceModule,
|
||||
contract: {
|
||||
...evmInterfaceModule.contract,
|
||||
outputSchema: z.strictObject({ unrelated: z.string() }),
|
||||
},
|
||||
}).not.toExtend<typeof evmInterfaceModule>();
|
||||
});
|
||||
|
||||
it("keeps the named binding and gives each caller a separate lazy service owner", async () => {
|
||||
expect(evmInterfaceModule.contract).toBe(INSPECT_EVM_INTERFACE_CONTRACT);
|
||||
expect(evmInterfaceModule.cliCommand).toBe(CLI_COMMANDS.inspectEvmInterface);
|
||||
const first = evmInterfaceModule.create({});
|
||||
const second = evmInterfaceModule.create({});
|
||||
try {
|
||||
expect(first).not.toBe(second);
|
||||
expect(first.provider).not.toBe(second.provider);
|
||||
await first.close();
|
||||
const inspect = vi.spyOn(second.provider, "inspect");
|
||||
const invalid = await second.inspect({
|
||||
path: "relative.hex",
|
||||
encoding: "hex",
|
||||
});
|
||||
expect(invalid).toMatchObject({
|
||||
ok: false,
|
||||
error: { _tag: "AnalysisInputError" },
|
||||
});
|
||||
expect(inspect).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
await first.close();
|
||||
await second.close();
|
||||
}
|
||||
});
|
||||
Loading…
Reference in New Issue