test(channels): shard registry-backed contracts

This commit is contained in:
Peter Steinberger
2026-04-20 23:04:42 +01:00
parent 35cb59e3b5
commit 9f9235b692
40 changed files with 297 additions and 60 deletions

View File

@@ -14,17 +14,26 @@ function listContractTestFiles(rootDir = "src/channels/plugins/contracts") {
const CONTRACT_FILE_WEIGHTS = new Map([
["channel-import-guardrails.test.ts", 18],
["directory.registry-backed.contract.test.ts", 12],
["outbound-payload.contract.test.ts", 18],
["plugin.registry-backed.contract.test.ts", 34],
["plugins-core.catalog.paths.contract.test.ts", 28],
["plugins-core.catalog.entries.contract.test.ts", 16],
["session-binding.registry-backed.contract.test.ts", 16],
["surfaces-only.registry-backed.contract.test.ts", 36],
]);
function resolveContractFileWeight(file) {
const name = file.replaceAll("\\", "/").split("/").pop();
if (name.startsWith("plugin.registry-backed-shard-")) {
return 5;
}
if (name.startsWith("surfaces-only.registry-backed-shard-")) {
return 5;
}
if (name.startsWith("directory.registry-backed-shard-")) {
return 4;
}
if (name.startsWith("threading.registry-backed-shard-")) {
return 4;
}
return CONTRACT_FILE_WEIGHTS.get(name) ?? 8;
}

View File

@@ -0,0 +1,3 @@
import { installDirectoryContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installDirectoryContractRegistryShard({ shardIndex: 0, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installDirectoryContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installDirectoryContractRegistryShard({ shardIndex: 1, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installDirectoryContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installDirectoryContractRegistryShard({ shardIndex: 2, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installDirectoryContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installDirectoryContractRegistryShard({ shardIndex: 3, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installDirectoryContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installDirectoryContractRegistryShard({ shardIndex: 4, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installDirectoryContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installDirectoryContractRegistryShard({ shardIndex: 5, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installDirectoryContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installDirectoryContractRegistryShard({ shardIndex: 6, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installDirectoryContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installDirectoryContractRegistryShard({ shardIndex: 7, shardCount: 8 });

View File

@@ -1,14 +0,0 @@
import { describe } from "vitest";
import { getDirectoryContractRegistry } from "../../../../test/helpers/channels/surface-contract-registry.js";
import { installChannelDirectoryContractSuite } from "../../../../test/helpers/channels/threading-directory-contract-suites.js";
for (const entry of getDirectoryContractRegistry()) {
describe(`${entry.id} directory contract`, () => {
installChannelDirectoryContractSuite({
plugin: entry.plugin,
coverage: entry.coverage,
cfg: entry.cfg,
accountId: entry.accountId,
});
});
}

View File

@@ -0,0 +1,3 @@
import { installPluginContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installPluginContractRegistryShard({ shardIndex: 0, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installPluginContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installPluginContractRegistryShard({ shardIndex: 1, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installPluginContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installPluginContractRegistryShard({ shardIndex: 2, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installPluginContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installPluginContractRegistryShard({ shardIndex: 3, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installPluginContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installPluginContractRegistryShard({ shardIndex: 4, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installPluginContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installPluginContractRegistryShard({ shardIndex: 5, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installPluginContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installPluginContractRegistryShard({ shardIndex: 6, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installPluginContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installPluginContractRegistryShard({ shardIndex: 7, shardCount: 8 });

View File

@@ -1,11 +0,0 @@
import { describe } from "vitest";
import { installChannelPluginContractSuite } from "../../../../test/helpers/channels/registry-contract-suites.js";
import { getPluginContractRegistry } from "../../../../test/helpers/channels/registry-plugin.js";
for (const entry of getPluginContractRegistry()) {
describe(`${entry.id} plugin contract`, () => {
installChannelPluginContractSuite({
plugin: entry.plugin,
});
});
}

View File

@@ -0,0 +1,3 @@
import { installSurfaceContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installSurfaceContractRegistryShard({ shardIndex: 0, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installSurfaceContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installSurfaceContractRegistryShard({ shardIndex: 1, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installSurfaceContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installSurfaceContractRegistryShard({ shardIndex: 2, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installSurfaceContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installSurfaceContractRegistryShard({ shardIndex: 3, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installSurfaceContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installSurfaceContractRegistryShard({ shardIndex: 4, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installSurfaceContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installSurfaceContractRegistryShard({ shardIndex: 5, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installSurfaceContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installSurfaceContractRegistryShard({ shardIndex: 6, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installSurfaceContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installSurfaceContractRegistryShard({ shardIndex: 7, shardCount: 8 });

View File

@@ -1,14 +0,0 @@
import { describe } from "vitest";
import { getSurfaceContractRegistry } from "../../../../test/helpers/channels/surface-contract-registry.js";
import { installChannelSurfaceContractSuite } from "../../../../test/helpers/channels/surface-contract-suite.js";
for (const entry of getSurfaceContractRegistry()) {
for (const surface of entry.surfaces) {
describe(`${entry.id} ${surface} surface contract`, () => {
installChannelSurfaceContractSuite({
plugin: entry.plugin,
surface,
});
});
}
}

View File

@@ -0,0 +1,3 @@
import { installThreadingContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installThreadingContractRegistryShard({ shardIndex: 0, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installThreadingContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installThreadingContractRegistryShard({ shardIndex: 1, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installThreadingContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installThreadingContractRegistryShard({ shardIndex: 2, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installThreadingContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installThreadingContractRegistryShard({ shardIndex: 3, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installThreadingContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installThreadingContractRegistryShard({ shardIndex: 4, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installThreadingContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installThreadingContractRegistryShard({ shardIndex: 5, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installThreadingContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installThreadingContractRegistryShard({ shardIndex: 6, shardCount: 8 });

View File

@@ -0,0 +1,3 @@
import { installThreadingContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
installThreadingContractRegistryShard({ shardIndex: 7, shardCount: 8 });

View File

@@ -1,11 +0,0 @@
import { describe } from "vitest";
import { getThreadingContractRegistry } from "../../../../test/helpers/channels/surface-contract-registry.js";
import { installChannelThreadingContractSuite } from "../../../../test/helpers/channels/threading-directory-contract-suites.js";
for (const entry of getThreadingContractRegistry()) {
describe(`${entry.id} threading contract`, () => {
installChannelThreadingContractSuite({
plugin: entry.plugin,
});
});
}

View File

@@ -0,0 +1,92 @@
import { describe, it } from "vitest";
import { installChannelPluginContractSuite } from "./registry-contract-suites.js";
import { getPluginContractRegistryShard } from "./registry-plugin.js";
import {
getDirectoryContractRegistryShard,
getSurfaceContractRegistryShard,
getThreadingContractRegistryShard,
} from "./surface-contract-registry.js";
import { installChannelSurfaceContractSuite } from "./surface-contract-suite.js";
import {
installChannelDirectoryContractSuite,
installChannelThreadingContractSuite,
} from "./threading-directory-contract-suites.js";
type ContractShardParams = {
shardIndex: number;
shardCount: number;
};
function installEmptyShardSuite(label: string) {
describe(label, () => {
it("has no matching bundled channels", () => {
// Keeps intentionally empty id-based shards visible to Vitest.
});
});
}
export function installSurfaceContractRegistryShard(params: ContractShardParams) {
const entries = getSurfaceContractRegistryShard(params);
if (entries.length === 0) {
installEmptyShardSuite("surface contract registry shard");
return;
}
for (const entry of entries) {
for (const surface of entry.surfaces) {
describe(`${entry.id} ${surface} surface contract`, () => {
installChannelSurfaceContractSuite({
plugin: entry.plugin,
surface,
});
});
}
}
}
export function installDirectoryContractRegistryShard(params: ContractShardParams) {
const entries = getDirectoryContractRegistryShard(params);
if (entries.length === 0) {
installEmptyShardSuite("directory contract registry shard");
return;
}
for (const entry of entries) {
describe(`${entry.id} directory contract`, () => {
installChannelDirectoryContractSuite({
plugin: entry.plugin,
coverage: entry.coverage,
cfg: entry.cfg,
accountId: entry.accountId,
});
});
}
}
export function installThreadingContractRegistryShard(params: ContractShardParams) {
const entries = getThreadingContractRegistryShard(params);
if (entries.length === 0) {
installEmptyShardSuite("threading contract registry shard");
return;
}
for (const entry of entries) {
describe(`${entry.id} threading contract`, () => {
installChannelThreadingContractSuite({
plugin: entry.plugin,
});
});
}
}
export function installPluginContractRegistryShard(params: ContractShardParams) {
const entries = getPluginContractRegistryShard(params);
if (entries.length === 0) {
installEmptyShardSuite("plugin contract registry shard");
return;
}
for (const entry of entries) {
describe(`${entry.id} plugin contract`, () => {
installChannelPluginContractSuite({
plugin: entry.plugin,
});
});
}
}

View File

@@ -1,4 +1,9 @@
import { listBundledChannelPlugins } from "../../../src/channels/plugins/bundled.js";
import {
getBundledChannelPlugin,
listBundledChannelPluginIds,
listBundledChannelPlugins,
} from "../../../src/channels/plugins/bundled.js";
import type { ChannelId } from "../../../src/channels/plugins/channel-id.types.js";
import { normalizeChannelMeta } from "../../../src/channels/plugins/meta-normalization.js";
import type { ChannelPlugin } from "../../../src/channels/plugins/types.js";
@@ -7,12 +12,35 @@ type PluginContractEntry = {
plugin: Pick<ChannelPlugin, "id" | "meta" | "capabilities" | "config">;
};
export function getPluginContractRegistry(): PluginContractEntry[] {
return listBundledChannelPlugins().map((plugin) => ({
function toPluginContractEntry(plugin: ChannelPlugin): PluginContractEntry {
return {
id: plugin.id,
plugin: {
...plugin,
meta: normalizeChannelMeta({ id: plugin.id, meta: plugin.meta }),
},
}));
};
}
function getBundledChannelPluginIdsForShard(params: {
shardIndex: number;
shardCount: number;
}): readonly ChannelId[] {
return listBundledChannelPluginIds().filter(
(_id, index) => index % params.shardCount === params.shardIndex,
);
}
export function getPluginContractRegistry(): PluginContractEntry[] {
return listBundledChannelPlugins().map(toPluginContractEntry);
}
export function getPluginContractRegistryShard(params: {
shardIndex: number;
shardCount: number;
}): PluginContractEntry[] {
return getBundledChannelPluginIdsForShard(params).flatMap((id) => {
const plugin = getBundledChannelPlugin(id);
return plugin ? [toPluginContractEntry(plugin)] : [];
});
}

View File

@@ -1,7 +1,10 @@
import {
getBundledChannelPlugin,
listBundledChannelPluginIds,
listBundledChannelPlugins,
setBundledChannelRuntime,
} from "../../../src/channels/plugins/bundled.js";
import type { ChannelId } from "../../../src/channels/plugins/channel-id.types.js";
import type { ChannelPlugin } from "../../../src/channels/plugins/types.js";
import type { OpenClawConfig } from "../../../src/config/config.js";
import {
@@ -53,18 +56,52 @@ setBundledChannelRuntime("line", {
} as never);
let surfaceContractRegistryCache: SurfaceContractEntry[] | undefined;
const surfaceContractEntryCache = new Map<ChannelId, SurfaceContractEntry | null>();
let threadingContractRegistryCache: ThreadingContractEntry[] | undefined;
let directoryContractRegistryCache: DirectoryContractEntry[] | undefined;
export function getSurfaceContractRegistry(): SurfaceContractEntry[] {
surfaceContractRegistryCache ??= listBundledChannelPlugins().map((plugin) => ({
function toSurfaceContractEntry(plugin: ChannelPlugin): SurfaceContractEntry {
return {
id: plugin.id,
plugin,
surfaces: channelPluginSurfaceKeys.filter((surface) => Boolean(plugin[surface])),
}));
};
}
function getBundledChannelPluginIdsForShard(params: {
shardIndex: number;
shardCount: number;
}): readonly ChannelId[] {
return listBundledChannelPluginIds().filter(
(_id, index) => index % params.shardCount === params.shardIndex,
);
}
function getSurfaceContractEntry(id: ChannelId): SurfaceContractEntry | undefined {
if (surfaceContractEntryCache.has(id)) {
return surfaceContractEntryCache.get(id) ?? undefined;
}
const plugin = getBundledChannelPlugin(id);
const entry = plugin ? toSurfaceContractEntry(plugin) : null;
surfaceContractEntryCache.set(id, entry);
return entry ?? undefined;
}
export function getSurfaceContractRegistry(): SurfaceContractEntry[] {
surfaceContractRegistryCache ??= listBundledChannelPlugins().map(toSurfaceContractEntry);
return surfaceContractRegistryCache;
}
export function getSurfaceContractRegistryShard(params: {
shardIndex: number;
shardCount: number;
}): SurfaceContractEntry[] {
return getBundledChannelPluginIdsForShard(params).flatMap((id) => {
const entry = getSurfaceContractEntry(id);
return entry ? [entry] : [];
});
}
export function getThreadingContractRegistry(): ThreadingContractEntry[] {
threadingContractRegistryCache ??= getSurfaceContractRegistry()
.filter((entry) => entry.surfaces.includes("threading"))
@@ -75,6 +112,18 @@ export function getThreadingContractRegistry(): ThreadingContractEntry[] {
return threadingContractRegistryCache;
}
export function getThreadingContractRegistryShard(params: {
shardIndex: number;
shardCount: number;
}): ThreadingContractEntry[] {
return getSurfaceContractRegistryShard(params)
.filter((entry) => entry.surfaces.includes("threading"))
.map((entry) => ({
id: entry.id,
plugin: entry.plugin,
}));
}
const directoryPresenceOnlyIds = new Set(["whatsapp", "zalouser"]);
export function getDirectoryContractRegistry(): DirectoryContractEntry[] {
@@ -87,3 +136,16 @@ export function getDirectoryContractRegistry(): DirectoryContractEntry[] {
}));
return directoryContractRegistryCache;
}
export function getDirectoryContractRegistryShard(params: {
shardIndex: number;
shardCount: number;
}): DirectoryContractEntry[] {
return getSurfaceContractRegistryShard(params)
.filter((entry) => entry.surfaces.includes("directory"))
.map((entry) => ({
id: entry.id,
plugin: entry.plugin,
coverage: directoryPresenceOnlyIds.has(entry.id) ? "presence" : "lookups",
}));
}