refactor: check type contracts without runtime test startup (#155099)

* test(plugin-sdk): move type checks into compiler contracts

* test: complete compiler contract migration

* fix: update keyed-store contract evidence path
This commit is contained in:
Peter Steinberger 2026-09-23 02:05:05 -07:00 • committed by GitHub
parent e331b97066
commit 71e7cf3b23
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 178 additions and 189 deletions

View file

@ -1,24 +0,0 @@
import { describe, expect, expectTypeOf, it } from "vitest";
import type { WorkboardBoardMetadata, WorkboardBoardSummary } from "./index.js";
describe("workboard board automation contract", () => {
it("carries the owning automation job reference in metadata and summaries", () => {
const metadata: WorkboardBoardMetadata = {
id: "planning",
automationJobId: "job-categorize-planning",
createdAt: 1,
updatedAt: 1,
};
const summary: WorkboardBoardSummary = {
id: metadata.id,
automationJobId: metadata.automationJobId,
total: 0,
active: 0,
archived: 0,
byStatus: {},
};
expect(summary.automationJobId).toBe("job-categorize-planning");
expectTypeOf(summary.automationJobId).toEqualTypeOf<string | undefined>();
});
});

View file

@ -1,41 +0,0 @@
import { describe, expect, expectTypeOf, it } from "vitest";
import type { agentCommandFromIngress } from "./agent-runtime.js";
import type { PluginRuntime } from "./core.js";
type PublicIngressOptions = Parameters<typeof agentCommandFromIngress>[0];
type PublicExecutionStarted = NonNullable<PublicIngressOptions["onExecutionStarted"]>;
type RuntimeIngressExecutionStarted = NonNullable<
Parameters<PluginRuntime["agent"]["runCommandFromIngress"]>[0]["onExecutionStarted"]
>;
type RuntimeEmbeddedExecutionStarted = NonNullable<
Parameters<PluginRuntime["agent"]["runEmbeddedAgent"]>[0]["onExecutionStarted"]
>;
const optionalRunIdCaller: PublicIngressOptions = {
message: "hello",
sessionKey: "agent:main:plugin-session",
allowModelOverride: false,
};
const privateRecoveryCorrelationIsHidden: "executionIdentityAdmission" extends keyof PublicIngressOptions
? false
: true = true;
describe("public agent ingress correlation contract", () => {
it("keeps runId optional and private execution recovery state unavailable", () => {
expect(optionalRunIdCaller).not.toHaveProperty("runId");
expect(privateRecoveryCorrelationIsHidden).toBe(true);
});
it("accepts ignored synchronous and asynchronous execution-start returns", () => {
const observed: string[] = [];
const synchronous = () => observed.push("started");
const asynchronous = async () => {
observed.push("started");
};
expectTypeOf(synchronous).toExtend<PublicExecutionStarted>();
expectTypeOf(asynchronous).toExtend<PublicExecutionStarted>();
expectTypeOf(synchronous).toExtend<RuntimeIngressExecutionStarted>();
expectTypeOf(asynchronous).toExtend<RuntimeIngressExecutionStarted>();
expectTypeOf(synchronous).toExtend<RuntimeEmbeddedExecutionStarted>();
expectTypeOf(asynchronous).toExtend<RuntimeEmbeddedExecutionStarted>();
});
});

View file

@ -1,18 +0,0 @@
import type { SessionManager } from "openclaw/plugin-sdk/agent-sessions";
import { describe, expectTypeOf, it } from "vitest";
describe("agent sessions SDK", () => {
it("keeps SessionManager.persist public", () => {
expectTypeOf<SessionManager["persist"]>().toBeFunction();
});
it("keeps tool-result preparation internal", () => {
expectTypeOf<SessionManager>().not.toHaveProperty("prepareModelVisibleToolText");
expectTypeOf<typeof import("openclaw/plugin-sdk/agent-sessions")>().not.toHaveProperty(
"prepareSessionToolResult",
);
expectTypeOf<typeof import("openclaw/plugin-sdk/agent-sessions")>().not.toHaveProperty(
"setSessionToolTextPreparer",
);
});
});

View file

@ -1,51 +0,0 @@
import { describe, expectTypeOf, it } from "vitest";
import {
resolveMemorySearchConfig,
resolveMemorySearchSyncConfig,
type ResolvedMemorySearchConfig,
} from "./memory-core-host-engine-foundation.js";
type IsOptional<T, K extends keyof T> = Pick<T, K> extends Required<Pick<T, K>> ? false : true;
describe("memory core host engine foundation contracts", () => {
it("preserves the released resolved-config authoring shapes", () => {
expectTypeOf<IsOptional<ResolvedMemorySearchConfig, "inputType">>().toEqualTypeOf<true>();
expectTypeOf<IsOptional<ResolvedMemorySearchConfig, "queryInputType">>().toEqualTypeOf<true>();
expectTypeOf<
IsOptional<ResolvedMemorySearchConfig, "documentInputType">
>().toEqualTypeOf<true>();
expectTypeOf<
IsOptional<ResolvedMemorySearchConfig, "outputDimensionality">
>().toEqualTypeOf<true>();
expectTypeOf<
IsOptional<ResolvedMemorySearchConfig["local"], "modelPath">
>().toEqualTypeOf<true>();
expectTypeOf<
IsOptional<ResolvedMemorySearchConfig["store"]["vector"], "extensionPath">
>().toEqualTypeOf<true>();
expectTypeOf<
ResolvedMemorySearchConfig["sync"]["embeddingBatchTimeoutSeconds"]
>().toEqualTypeOf<number | undefined>();
const local: ResolvedMemorySearchConfig["local"] = {};
const vector: ResolvedMemorySearchConfig["store"]["vector"] = { enabled: true };
expectTypeOf(local).toMatchTypeOf<ResolvedMemorySearchConfig["local"]>();
expectTypeOf(vector).toMatchTypeOf<ResolvedMemorySearchConfig["store"]["vector"]>();
});
it("preserves the released resolver return contracts", () => {
expectTypeOf<
ReturnType<typeof resolveMemorySearchConfig>
>().toEqualTypeOf<ResolvedMemorySearchConfig | null>();
expectTypeOf<ReturnType<typeof resolveMemorySearchSyncConfig>>().toEqualTypeOf<
ResolvedMemorySearchConfig["sync"] | null
>();
expectTypeOf<
NonNullable<ReturnType<typeof resolveMemorySearchConfig>>["local"]["contextSize"]
>().toEqualTypeOf<number | "auto" | undefined>();
expectTypeOf<
NonNullable<ReturnType<typeof resolveMemorySearchSyncConfig>>["embeddingBatchTimeoutSeconds"]
>().toEqualTypeOf<number | undefined>();
});
});

View file

@ -1,26 +0,0 @@
import { describe, expectTypeOf, it } from "vitest";
import type { PluginStateKeyedStore, PluginStateSyncKeyedStore } from "./plugin-state-runtime.js";
import type {
createPluginStateKeyedStore,
createPluginStateSyncKeyedStore,
} from "./plugin-state-store-runtime.js";
describe("plugin state store type contracts", () => {
it("guarantees atomic capabilities from the concrete SDK factory", () => {
expectTypeOf<ReturnType<typeof createPluginStateKeyedStore<{ count: number }>>>().toEqualTypeOf<
Required<PluginStateKeyedStore<{ count: number }>>
>();
expectTypeOf<
ReturnType<typeof createPluginStateSyncKeyedStore<{ count: number }>>
>().toEqualTypeOf<Required<PluginStateSyncKeyedStore<{ count: number }>>>();
});
it("allows general stores without optional capabilities", () => {
expectTypeOf<
Omit<PluginStateKeyedStore<{ count: number }>, "update" | "deleteIf" | "lookupMany">
>().toExtend<PluginStateKeyedStore<{ count: number }>>();
expectTypeOf<
Omit<PluginStateSyncKeyedStore<{ count: number }>, "update" | "deleteIf" | "lookupMany">
>().toExtend<PluginStateSyncKeyedStore<{ count: number }>>();
});
});

View file

@ -121,7 +121,7 @@ export const PLUGIN_COMPAT_RECORDS = [
"src/plugins/compat/registry.test.ts",
"src/plugin-state/plugin-state-store.test.ts",
"src/plugin-state/plugin-state-store.runtime.test.ts",
"src/plugin-sdk/plugin-state-store-runtime.test.ts",
"test/type-contracts/plugin-state-store-runtime.ts",
"src/plugins/loader.runtime-registry.test.ts",
],
releaseNote:

View file

@ -133,9 +133,9 @@ describe("type suppression inventory", () => {
"src/infra/net/fetch-guard.socks.test.ts:@ts-expect-error Undici's Node TLS intersection rejects its runtime-valid null timeout.",
"src/infra/net/fetch-guard.socks.test.ts:@ts-expect-error Undici's Node TLS intersection rejects its runtime-valid null timeout.",
"src/infra/net/fetch-guard.socks.test.ts:@ts-expect-error Undici's Node TLS intersection rejects its runtime-valid null timeout.",
"src/plugin-sdk/plugin-entry.reply-trigger.test.ts:@ts-expect-error Trigger eligibility is only supported for before_agent_reply.",
"src/plugin-sdk/plugin-entry.reply-trigger.test.ts:@ts-expect-error An empty trigger list cannot prove that a hook is inactive.",
"src/plugin-sdk/plugin-entry.reply-trigger.test.ts:@ts-expect-error Tool authority is only supported for before_prompt_build.",
"test/type-contracts/plugin-entry-hook-options.ts:@ts-expect-error Trigger eligibility is only supported for before_agent_reply.",
"test/type-contracts/plugin-entry-hook-options.ts:@ts-expect-error An empty trigger list cannot prove that a hook is inactive.",
"test/type-contracts/plugin-entry-hook-options.ts:@ts-expect-error Tool authority is only supported for before_prompt_build.",
"src/plugins/registry.diagnostics.test.ts:@ts-expect-error JavaScript plugins may omit the required supplement builder.",
"src/plugins/registry.diagnostics.test.ts:@ts-expect-error JavaScript plugins may omit the required hosted-media resolver.",
"src/plugins/registry.diagnostics.test.ts:@ts-expect-error Unknown JavaScript hook names must produce a diagnostic.",

View file

@ -0,0 +1,38 @@
import { expectTypeOf } from "vitest";
import type { agentCommandFromIngress } from "../../src/plugin-sdk/agent-runtime.js";
import type { PluginRuntime } from "../../src/plugin-sdk/core.js";
type PublicIngressOptions = Parameters<typeof agentCommandFromIngress>[0];
type PublicExecutionStarted = NonNullable<PublicIngressOptions["onExecutionStarted"]>;
type RuntimeIngressExecutionStarted = NonNullable<
Parameters<PluginRuntime["agent"]["runCommandFromIngress"]>[0]["onExecutionStarted"]
>;
type RuntimeEmbeddedExecutionStarted = NonNullable<
Parameters<PluginRuntime["agent"]["runEmbeddedAgent"]>[0]["onExecutionStarted"]
>;
// public agent ingress correlation contract
// keeps runId optional and private execution recovery state unavailable
const optionalRunIdCaller: PublicIngressOptions = {
message: "hello",
sessionKey: "agent:main:plugin-session",
allowModelOverride: false,
};
const privateRecoveryCorrelationIsHidden: "executionIdentityAdmission" extends keyof PublicIngressOptions
? false
: true = true;
void optionalRunIdCaller;
void privateRecoveryCorrelationIsHidden;
// accepts ignored synchronous and asynchronous execution-start returns
const observed: string[] = [];
const synchronous = () => observed.push("started");
const asynchronous = async () => {
observed.push("started");
};
expectTypeOf(synchronous).toExtend<PublicExecutionStarted>();
expectTypeOf(asynchronous).toExtend<PublicExecutionStarted>();
expectTypeOf(synchronous).toExtend<RuntimeIngressExecutionStarted>();
expectTypeOf(asynchronous).toExtend<RuntimeIngressExecutionStarted>();
expectTypeOf(synchronous).toExtend<RuntimeEmbeddedExecutionStarted>();
expectTypeOf(asynchronous).toExtend<RuntimeEmbeddedExecutionStarted>();

View file

@ -0,0 +1,15 @@
import type { SessionManager } from "openclaw/plugin-sdk/agent-sessions";
import { expectTypeOf } from "vitest";
// agent sessions SDK
// keeps SessionManager.persist public
expectTypeOf<SessionManager["persist"]>().toBeFunction();
// keeps tool-result preparation internal
expectTypeOf<SessionManager>().not.toHaveProperty("prepareModelVisibleToolText");
expectTypeOf<typeof import("openclaw/plugin-sdk/agent-sessions")>().not.toHaveProperty(
"prepareSessionToolResult",
);
expectTypeOf<typeof import("openclaw/plugin-sdk/agent-sessions")>().not.toHaveProperty(
"setSessionToolTextPreparer",
);

View file

@ -0,0 +1,44 @@
import { expectTypeOf } from "vitest";
import {
resolveMemorySearchConfig,
resolveMemorySearchSyncConfig,
type ResolvedMemorySearchConfig,
} from "../../src/plugin-sdk/memory-core-host-engine-foundation.js";
type IsOptional<T, K extends keyof T> = Pick<T, K> extends Required<Pick<T, K>> ? false : true;
// memory core host engine foundation contracts
// preserves the released resolved-config authoring shapes
expectTypeOf<IsOptional<ResolvedMemorySearchConfig, "inputType">>().toEqualTypeOf<true>();
expectTypeOf<IsOptional<ResolvedMemorySearchConfig, "queryInputType">>().toEqualTypeOf<true>();
expectTypeOf<IsOptional<ResolvedMemorySearchConfig, "documentInputType">>().toEqualTypeOf<true>();
expectTypeOf<
IsOptional<ResolvedMemorySearchConfig, "outputDimensionality">
>().toEqualTypeOf<true>();
expectTypeOf<IsOptional<ResolvedMemorySearchConfig["local"], "modelPath">>().toEqualTypeOf<true>();
expectTypeOf<
IsOptional<ResolvedMemorySearchConfig["store"]["vector"], "extensionPath">
>().toEqualTypeOf<true>();
expectTypeOf<ResolvedMemorySearchConfig["sync"]["embeddingBatchTimeoutSeconds"]>().toEqualTypeOf<
number | undefined
>();
const local: ResolvedMemorySearchConfig["local"] = {};
const vector: ResolvedMemorySearchConfig["store"]["vector"] = { enabled: true };
expectTypeOf(local).toMatchTypeOf<ResolvedMemorySearchConfig["local"]>();
expectTypeOf(vector).toMatchTypeOf<ResolvedMemorySearchConfig["store"]["vector"]>();
// preserves the released resolver return contracts
expectTypeOf<
ReturnType<typeof resolveMemorySearchConfig>
>().toEqualTypeOf<ResolvedMemorySearchConfig | null>();
expectTypeOf<ReturnType<typeof resolveMemorySearchSyncConfig>>().toEqualTypeOf<
ResolvedMemorySearchConfig["sync"] | null
>();
expectTypeOf<
NonNullable<ReturnType<typeof resolveMemorySearchConfig>>["local"]["contextSize"]
>().toEqualTypeOf<number | "auto" | undefined>();
expectTypeOf<
NonNullable<ReturnType<typeof resolveMemorySearchSyncConfig>>["embeddingBatchTimeoutSeconds"]
>().toEqualTypeOf<number | undefined>();

View file

@ -1,6 +1,10 @@
import { describe, expectTypeOf, it } from "vitest";
import type { OpenClawPluginApi, WorkerMachineOption, WorkerProvider } from "./plugin-entry.js";
import type { PluginHookAgentTrigger } from "./types.js";
import { expectTypeOf } from "vitest";
import type {
OpenClawPluginApi,
WorkerMachineOption,
WorkerProvider,
} from "../../src/plugin-sdk/plugin-entry.js";
import type { PluginHookAgentTrigger } from "../../src/plugin-sdk/types.js";
function registerScopedReplyHook(api: OpenClawPluginApi): void {
api.on("before_agent_reply", async () => undefined, { eligibleTriggers: ["heartbeat", "cron"] });
@ -32,25 +36,23 @@ function registerAuthorizedPromptHook(api: OpenClawPluginApi): void {
void registerScopedReplyHook;
void registerAuthorizedPromptHook;
describe("plugin entry hook option contracts", () => {
it("exposes scoped reply and prompt authority options through the public plugin API", () => {
expectTypeOf<OpenClawPluginApi["on"]>().toBeFunction();
expectTypeOf<PluginHookAgentTrigger>().toEqualTypeOf<"cron" | "heartbeat" | "user">();
expectTypeOf<WorkerMachineOption>().toEqualTypeOf<{
readonly id: string;
readonly label: string;
readonly cpu?: number;
readonly memoryGb?: number;
readonly default?: boolean;
readonly os?: string;
}>();
expectTypeOf<
Awaited<ReturnType<NonNullable<WorkerProvider["listOperatingSystems"]>>>[number]
>().toEqualTypeOf<{
readonly id: string;
readonly label: string;
readonly default?: boolean;
readonly disabledReason?: string;
}>();
});
});
// plugin entry hook option contracts
// exposes scoped reply and prompt authority options through the public plugin API
expectTypeOf<OpenClawPluginApi["on"]>().toBeFunction();
expectTypeOf<PluginHookAgentTrigger>().toEqualTypeOf<"cron" | "heartbeat" | "user">();
expectTypeOf<WorkerMachineOption>().toEqualTypeOf<{
readonly id: string;
readonly label: string;
readonly cpu?: number;
readonly memoryGb?: number;
readonly default?: boolean;
readonly os?: string;
}>();
expectTypeOf<
Awaited<ReturnType<NonNullable<WorkerProvider["listOperatingSystems"]>>>[number]
>().toEqualTypeOf<{
readonly id: string;
readonly label: string;
readonly default?: boolean;
readonly disabledReason?: string;
}>();

View file

@ -0,0 +1,26 @@
import { expectTypeOf } from "vitest";
import type {
PluginStateKeyedStore,
PluginStateSyncKeyedStore,
} from "../../src/plugin-sdk/plugin-state-runtime.js";
import type {
createPluginStateKeyedStore,
createPluginStateSyncKeyedStore,
} from "../../src/plugin-sdk/plugin-state-store-runtime.js";
// plugin state store type contracts
// guarantees atomic capabilities from the concrete SDK factory
expectTypeOf<ReturnType<typeof createPluginStateKeyedStore<{ count: number }>>>().toEqualTypeOf<
Required<PluginStateKeyedStore<{ count: number }>>
>();
expectTypeOf<ReturnType<typeof createPluginStateSyncKeyedStore<{ count: number }>>>().toEqualTypeOf<
Required<PluginStateSyncKeyedStore<{ count: number }>>
>();
// allows general stores without optional capabilities
expectTypeOf<
Omit<PluginStateKeyedStore<{ count: number }>, "update" | "deleteIf" | "lookupMany">
>().toExtend<PluginStateKeyedStore<{ count: number }>>();
expectTypeOf<
Omit<PluginStateSyncKeyedStore<{ count: number }>, "update" | "deleteIf" | "lookupMany">
>().toExtend<PluginStateSyncKeyedStore<{ count: number }>>();

View file

@ -0,0 +1,24 @@
import { expectTypeOf } from "vitest";
import type {
WorkboardBoardMetadata,
WorkboardBoardSummary,
} from "../../packages/workboard-contract/src/index.js";
// workboard board automation contract
// carries the owning automation job reference in metadata and summaries
const metadata: WorkboardBoardMetadata = {
id: "planning",
automationJobId: "job-categorize-planning",
createdAt: 1,
updatedAt: 1,
};
const summary: WorkboardBoardSummary = {
id: metadata.id,
automationJobId: metadata.automationJobId,
total: 0,
active: 0,
archived: 0,
byStatus: {},
};
expectTypeOf(summary.automationJobId).toEqualTypeOf<string | undefined>();