refactor: correct SDK compatibility removal metadata (#115862)

* refactor(plugin-sdk): correct compat removal metadata

* test(plugins): align boundary report compat counts
This commit is contained in:
Peter Steinberger
2026-07-29 08:18:52 -04:00
committed by GitHub
parent edf4aca7bc
commit 5de970c1cc
5 changed files with 78 additions and 64 deletions

View File

@@ -129,55 +129,56 @@ export const DEPRECATED_PLUGIN_SDK_SUBPATH_RECORDS = DEPRECATED_PLUGIN_SDK_SUBPA
}) satisfies PluginCompatRecord,
) satisfies readonly PluginCompatRecord[];
const BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATHS = [
"media-understanding",
"memory-host-core",
"plugin-config-runtime",
"tool-plugin",
] as const;
type SdkReplacement = { replacement: string; docsPath: string };
const DOCUMENTED_PUBLIC_PLUGIN_SDK_REPLACEMENTS: Record<string, SdkReplacement> = {
"media-understanding": {
const BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATH_SEEDS = [
{
subpath: "media-understanding",
status: "removal-pending",
removeAfter: "2026-09-30",
replacement:
"`api.registerMediaUnderstandingProvider(...)` with provider-owned request helpers and types from `openclaw/plugin-sdk/plugin-entry`",
"`api.registerMediaUnderstandingProvider(...)` with provider-owned request helpers and types from `openclaw/plugin-sdk/plugin-entry`; retain the public subpath through the 2026-09-30 window while official plugin consumers migrate",
docsPath: "/plugins/architecture",
},
"memory-host-core": {
{
subpath: "memory-host-core",
status: "removal-pending",
removeAfter: "2026-09-30",
replacement:
"host-prepared memory prompts via `openclaw/plugin-sdk/core` and memory capability registration through the injected plugin API; retain the facade for companion-plugin public-artifact discovery until a focused read seam exists",
"host-prepared memory prompts via `openclaw/plugin-sdk/core` and memory capability registration through the injected plugin API; retain the facade through the 2026-09-30 window and until a focused public-artifact read seam exists",
docsPath: "/plugins/architecture-internals#context-engine-plugins",
},
"plugin-config-runtime": {
{
subpath: "plugin-config-runtime",
status: "removal-pending",
removeAfter: "2026-12-01",
replacement:
"`api.pluginConfig`, runtime tool context config, and focused `config-contracts`, `runtime-config-snapshot`, or `config-mutation` subpaths",
"`api.pluginConfig`, runtime tool context config, and focused `config-contracts`, `runtime-config-snapshot`, or `config-mutation` subpaths; retain the public subpath through the 2026-12-01 window while official plugin consumers migrate",
docsPath: "/plugins/sdk-runtime",
},
};
{
subpath: "tool-plugin",
status: "deprecated",
replacement:
"retain the public subpath until plugin authoring has a nonexecuting static metadata replacement for `defineToolPlugin`; `getToolPluginMetadata` currently reads metadata only from an already-executed entry",
docsPath: "/plugins/tool-plugins",
},
] as const;
function buildPublicSdkSubpathRecord({
subpath,
...compat
}: (typeof BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATH_SEEDS)[number]) {
return {
code: `plugin-sdk-${subpath}-public-demotion` as const,
owner: "sdk" as const,
introduced: "2026-07-15",
deprecated: "2026-07-15",
warningStarts: "2026-07-15",
...compat,
surfaces: [`openclaw/plugin-sdk/${subpath}`],
diagnostics: ["registry-backed public SDK demotion window; no external runtime import warning"],
tests: ["src/plugins/compat/registry.test.ts"],
} satisfies PluginCompatRecord;
}
export const BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATH_RECORDS =
BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATHS.map((subpath) => {
const documented = DOCUMENTED_PUBLIC_PLUGIN_SDK_REPLACEMENTS[subpath];
return {
code: `plugin-sdk-${subpath}-public-demotion` as const,
status: "removal-pending" as const,
owner: "sdk" as const,
introduced: "2026-07-15",
deprecated: "2026-07-15",
warningStarts: "2026-07-15",
removeAfter: "2026-07-30",
replacement:
subpath === "tool-plugin"
? "retain the public subpath until plugin authoring has a nonexecuting static metadata replacement for `defineToolPlugin`"
: `${documented?.replacement ?? "define and document a public replacement"}; retain the public subpath until the 2026-07-30 window closes and official plugin consumers migrate`,
docsPath:
subpath === "tool-plugin"
? "/plugins/tool-plugins"
: (documented?.docsPath ?? "/plugins/sdk-migration"),
surfaces: [`openclaw/plugin-sdk/${subpath}`],
diagnostics: [
"registry-backed public SDK demotion window; no external runtime import warning",
],
tests: ["src/plugins/compat/registry.test.ts"],
} satisfies PluginCompatRecord;
}) satisfies readonly PluginCompatRecord[];
BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATH_SEEDS.map(buildPublicSdkSubpathRecord);

View File

@@ -440,14 +440,12 @@ export const PLUGIN_COMPAT_RECORDS = [
},
{
code: "agent-harness-sdk-alias",
status: "removal-pending",
status: "deprecated",
owner: "agent-runtime",
introduced: "2026-04-24",
deprecated: "2026-04-25",
warningStarts: "2026-04-25",
removeAfter: "2026-07-25",
replacement:
"`openclaw/plugin-sdk/agent-runtime`; retain the public aliases until the shipped SDK contract has a replacement window backed by external-usage proof",
replacement: "none yet; retain until a harness subpath ships and external migration is proven",
docsPath: "/plugins/sdk-agent-harness",
surfaces: ["openclaw/plugin-sdk/agent-harness", "openclaw/plugin-sdk/agent-harness-runtime"],
diagnostics: ["plugin SDK compatibility warning"],

View File

@@ -2,7 +2,7 @@
import fs from "node:fs";
import { beforeAll, describe, expect, it } from "vitest";
import { listGitTrackedFiles } from "../../test-utils/repo-files.js";
import { listPluginCompatRecords } from "./registry.js";
import { listPluginCompatRecords, type PluginCompatCode } from "./registry.js";
const datePattern = /^\d{4}-\d{2}-\d{2}$/u;
const sourceRootsForDeprecatedCallGuard = [
@@ -21,7 +21,10 @@ const deprecatedTargetParserCompatFiles = new Set([
"src/infra/outbound/outbound-session.test-helpers.ts",
"src/plugins/compat/registry.test.ts",
]);
const removalDatePendingCompatCodes = new Set<string>();
const removalDatePendingCompatCodes = new Set<PluginCompatCode>([
"plugin-sdk-tool-plugin-public-demotion",
"agent-harness-sdk-alias",
]);
const deprecationMarkingCodes = [
"plugin-sdk-channel-setup-input-fields",
"plugin-sdk-broad-runtime-barrels",
@@ -91,25 +94,37 @@ describe("plugin compatibility registry", () => {
}
});
it("keeps shipped public contracts pending until their runtime blockers clear", () => {
const records = listPluginCompatRecords().filter(
it("keeps blocked public SDK removals aligned with their actual gates", () => {
const records = new Map(listPluginCompatRecords().map((record) => [record.code, record]));
const staleRemovalWindows = [...records.values()].filter(
(record) =>
record.status === "removal-pending" &&
record.removeAfter !== undefined &&
record.removeAfter <= "2026-07-30",
);
expect(records.map((record) => record.code)).toEqual([
"plugin-sdk-media-understanding-public-demotion",
"plugin-sdk-memory-host-core-public-demotion",
"plugin-sdk-plugin-config-runtime-public-demotion",
"plugin-sdk-tool-plugin-public-demotion",
"agent-harness-sdk-alias",
]);
for (const record of records) {
expect(record.replacement).toMatch(/retain the public/u);
expect(record.releaseNote).toBeUndefined();
expect(staleRemovalWindows).toEqual([]);
expect(records.get("plugin-sdk-media-understanding-public-demotion")).toMatchObject({
status: "removal-pending",
removeAfter: "2026-09-30",
});
expect(records.get("plugin-sdk-memory-host-core-public-demotion")).toMatchObject({
status: "removal-pending",
removeAfter: "2026-09-30",
});
expect(records.get("plugin-sdk-plugin-config-runtime-public-demotion")).toMatchObject({
status: "removal-pending",
removeAfter: "2026-12-01",
});
for (const code of removalDatePendingCompatCodes) {
expect(records.get(code)).toMatchObject({ status: "deprecated" });
expect(records.get(code)?.removeAfter).toBeUndefined();
expect(records.get(code)?.replacement).toMatch(/retain/u);
}
expect(records.get("agent-harness-sdk-alias")?.surfaces).toEqual([
"openclaw/plugin-sdk/agent-harness",
"openclaw/plugin-sdk/agent-harness-runtime",
]);
});
it("tracks the deprecation-marking families through the approved window", () => {

View File

@@ -1232,6 +1232,8 @@ describe("plugin-sdk subpath exports", () => {
]);
expectSourceMentions("setup-tools", ["formatCliCommand", "detectBinary", "formatDocsLink"]);
expectSourceMentions("lazy-runtime", ["createLazyRuntimeSurface", "createLazyRuntimeModule"]);
expectSourceMentions("agent-harness", ["./agent-harness-runtime.js"]);
expectSourceMentions("agent-harness-runtime", ["AgentHarness", "EmbeddedPiCompactResult"]);
expectSourceOmitsSnippet("agent-runtime", "./sglang.js");
expectSourceOmitsSnippet("agent-runtime", "./vllm.js");
expectSourceOmitsSnippet("agent-runtime", "../../extensions/");

View File

@@ -54,14 +54,12 @@ describe("plugin-boundary-report", () => {
expect(summaryResult.exitCode).toBe(0);
expect(summaryResult.stderr).toBe("");
expect(summary.compat?.removalPendingCount).toBe(5);
expect(summary.compat?.removalPendingCount).toBe(3);
expect(summary.compat?.removalPendingDueCount).toEqual(expect.any(Number));
expect(summary.compat?.removalPending?.map((record) => record.code)).toEqual([
"agent-harness-sdk-alias",
"plugin-sdk-media-understanding-public-demotion",
"plugin-sdk-memory-host-core-public-demotion",
"plugin-sdk-plugin-config-runtime-public-demotion",
"plugin-sdk-tool-plugin-public-demotion",
]);
for (const record of summary.compat?.removalPending ?? []) {
expect(record.removeAfter).toMatch(/^\d{4}-\d{2}-\d{2}$/u);
@@ -84,8 +82,8 @@ describe("plugin-boundary-report", () => {
expect(result.exitCode).toBe(0);
expect(result.stderr).toBe("");
expect(result.stdout).toContain("removalPending=5");
expect(result.stdout).toContain("removal-pending 2026-07-25 agent-harness-sdk-alias");
expect(result.stdout).toContain("removalPending=3");
expect(result.stdout).not.toContain("agent-harness-sdk-alias");
expect(result.stdout).toMatch(/blocker=.*retain the public/iu);
expect(result.stdout).toMatch(/readerRefs=\d+ readers=/u);
});