From 68ce72ea6b5bf01eae3227c87c2ae5e4b614eb1f Mon Sep 17 00:00:00 2001 From: devSejung Date: Sat, 24 Oct 2026 06:32:03 +0900 Subject: [PATCH 1/2] fix: respect session-store retirement conditions in API checks --- docs/plugins/compatibility.md | 40 ++--- docs/plugins/sdk-migration.md | 13 +- src/plugins/compat/registry-records.ts | 32 +++- src/plugins/compat/registry.test.ts | 47 ++++-- test/scripts/plugin-boundary-report.test.ts | 156 ++++++++++++-------- 5 files changed, 187 insertions(+), 101 deletions(-) diff --git a/docs/plugins/compatibility.md b/docs/plugins/compatibility.md index 1db12dbef6..60a0f87b62 100644 --- a/docs/plugins/compatibility.md +++ b/docs/plugins/compatibility.md @@ -88,25 +88,27 @@ October 1 annotation families are `removal-pending`, with their original dates and removal conditions preserved in the review queue. This does not claim completed migration of published readers. -| Compatibility code | Removal condition | `removeAfter` | -| ----------------------------------------- | ------------------------------------------------------------------------------------------------------- | ------------- | -| `plugin-sdk-channel-setup-input-fields` | Repeat the published-plugin artifact sweep and remove only fields with no reader. | 2026-10-01 | -| `plugin-sdk-broad-runtime-barrels` | Move bundled and indexed external consumers to focused SDK subpaths. | 2026-10-01 | -| `plugin-sdk-provider-owned-helper-shims` | Move each deprecated provider helper to its provider-local API and prove no published reader remains. | 2026-10-01 | -| `message-presentation-legacy-bridges` | Move reply producers and official channel packages to `MessagePresentation`. | 2026-10-01 | -| `plugin-sdk-focused-compat-aliases` | Prove every enumerated alias has no bundled or published reader. | 2026-10-01 | -| `agent-harness-terminal-result-aliases` | Move harnesses to `terminal` and `visibleReplies`, then prove the legacy result fields are unread. | 2026-10-01 | -| `official-plugin-export-aliases` | Move users of Google Meet testing, channel presentation, and Discord timeout exports to canonical APIs. | 2026-10-01 | -| `memory-host-compatibility-aliases` | Use canonical memory tables while preserving existing custom-table data during migration. | 2026-10-01 | -| `plugin-runtime-api-compat-aliases` | Move flat plugin registration/runtime calls to their namespaced or focused replacements. | 2026-10-01 | -| `plugin-provider-manifest-compat-aliases` | Move kind/setup/catalog ownership to manifests and model-catalog registration. | 2026-10-01 | -| `deprecated-session-store-beta5-api` | End the v2026.7.x whole-store upgrade window, including package-root aliases. | 2026-10-12 | - -The beta.5 session-store record is `removal-pending` with its original October 12 -review date. Removal still requires verified published-reader migration, a minimum -supported plugin version that excludes beta.5, closure of the legacy doctor upgrade -window, and explicit breaking-release approval. The elapsed date does not establish -that these conditions are satisfied. +| Compatibility code | Removal condition | `removeAfter` | +| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ------------- | +| `plugin-sdk-channel-setup-input-fields` | Repeat the published-plugin artifact sweep and remove only fields with no reader. | 2026-10-01 | +| `plugin-sdk-broad-runtime-barrels` | Move bundled and indexed external consumers to focused SDK subpaths. | 2026-10-01 | +| `plugin-sdk-provider-owned-helper-shims` | Move each deprecated provider helper to its provider-local API and prove no published reader remains. | 2026-10-01 | +| `message-presentation-legacy-bridges` | Move reply producers and official channel packages to `MessagePresentation`. | 2026-10-01 | +| `plugin-sdk-focused-compat-aliases` | Prove every enumerated alias has no bundled or published reader. | 2026-10-01 | +| `agent-harness-terminal-result-aliases` | Move harnesses to `terminal` and `visibleReplies`, then prove the legacy result fields are unread. | 2026-10-01 | +| `official-plugin-export-aliases` | Move users of Google Meet testing, channel presentation, and Discord timeout exports to canonical APIs. | 2026-10-01 | +| `memory-host-compatibility-aliases` | Use canonical memory tables while preserving existing custom-table data during migration. | 2026-10-01 | +| `plugin-runtime-api-compat-aliases` | Move flat plugin registration/runtime calls to their namespaced or focused replacements. | 2026-10-01 | +| `plugin-provider-manifest-compat-aliases` | Move kind/setup/catalog ownership to manifests and model-catalog registration. | 2026-10-01 | +| `deprecated-session-store-beta5-api` | Exclude v2026.7.1-beta.5 from the minimum supported plugin version before removing its four SDK helpers. | 2026-10-12 | +| `deprecated-package-root-session-store-api` | End the v2026.7.x upgrade window that requires the legacy doctor importer before removing package-root aliases. | 2026-10-12 | + +The two session-store records are `removal-pending`, with the original review +date preserved. Both require verified published-reader migration and explicit +breaking-release approval. The SDK bridge additionally requires a minimum +supported plugin version that excludes beta.5; package-root aliases require +closure of the v2026.7.x legacy doctor upgrade window. The elapsed date does not +establish that these conditions are satisfied. Discord and Slack retain their published legacy renderer exports as conversion adapters to the canonical presentation renderers. Bundled channel callers use diff --git a/docs/plugins/sdk-migration.md b/docs/plugins/sdk-migration.md index 36d65b4b4d..b45c6b173c 100644 --- a/docs/plugins/sdk-migration.md +++ b/docs/plugins/sdk-migration.md @@ -863,10 +863,15 @@ timeline for current status. Official plugins released with `v2026.7.1-beta.5` imported the four deprecated helpers above. `openclaw/plugin-sdk/session-store-runtime` keeps - that exact bridge pending its existing published-reader and legacy doctor - upgrade conditions, with 2026-10-12 preserved as the review date. Removal - requires verified exclusion of beta.5 from supported plugins and explicit - breaking-release approval; new plugins must use the replacements. + that exact bridge pending verified published-reader migration and exclusion + of that release from the minimum supported plugin version, with 2026-10-12 + preserved as the review date. Removal also requires explicit breaking-release + approval; new plugins must use the replacements. + Package-root `loadSessionStore(...)` and `saveSessionStore(...)` + retain their separate condition: the v2026.7.x upgrade window must no longer + require the legacy doctor importer before those aliases are removed. + Published-reader migration must also be verified and explicit breaking-release + approval granted for those aliases. `resolveStorePath(...)` remains a supported SDK helper and is not part of this deprecation. diff --git a/src/plugins/compat/registry-records.ts b/src/plugins/compat/registry-records.ts index d4cf40a50e..20d02c468e 100644 --- a/src/plugins/compat/registry-records.ts +++ b/src/plugins/compat/registry-records.ts @@ -91,6 +91,8 @@ export const PLUGIN_COMPAT_RECORDS = [ }, { code: "deprecated-session-store-beta5-api", + // The SDK bridge has a published-reader gate; the package-root aliases below + // have a separate doctor upgrade gate. Elapsed dates alone retire neither. status: "removal-pending", owner: "sdk", introduced: "2026-05-21", @@ -98,24 +100,38 @@ export const PLUGIN_COMPAT_RECORDS = [ warningStarts: "2026-07-12", removeAfter: "2026-10-12", replacement: - "`getSessionEntry(...)`, `listSessionEntries(...)`, and row-level session mutations; retain the beta.5 bridge and package-root aliases until published-reader migration and minimum supported plugin exclusion are verified, the v2026.7.x legacy doctor upgrade window closes, and explicit breaking-release approval is granted", + "`getSessionEntry(...)`, `listSessionEntries(...)`, and row-level session mutations; retain the four SDK helpers until published-reader migration and minimum supported plugin exclusion of v2026.7.1-beta.5 are verified and explicit breaking-release approval is granted", docsPath: "/plugins/sdk-migration#removed-session-and-transcript-file-apis", surfaces: [ "openclaw/plugin-sdk/session-store-runtime loadSessionStore", "openclaw/plugin-sdk/session-store-runtime updateSessionStore", "openclaw/plugin-sdk/session-store-runtime resolveSessionFilePath", "openclaw/plugin-sdk/session-store-runtime resolveSessionStoreEntry", - "openclaw package root loadSessionStore", - "openclaw package root saveSessionStore", ], diagnostics: ["plugin SDK deprecation"], - tests: [ - "src/plugin-sdk/session-store-runtime.test.ts", - "src/index.test.ts", - "src/plugins/compat/registry.test.ts", + tests: ["src/plugin-sdk/session-store-runtime.test.ts", "src/plugins/compat/registry.test.ts"], + releaseNote: + "The beta.5 session-store import set remains available until the supported plugin floor excludes that release; the original review date is unchanged.", + }, + { + code: "deprecated-package-root-session-store-api", + status: "removal-pending", + owner: "core", + introduced: "2026-05-21", + deprecated: "2026-07-12", + warningStarts: "2026-07-12", + removeAfter: "2026-10-12", + replacement: + "SQLite-backed session APIs; retain the package-root aliases until published-reader migration is verified, the v2026.7.x upgrade window no longer requires the legacy doctor importer, and explicit breaking-release approval is granted", + docsPath: "/plugins/sdk-migration#removed-session-and-transcript-file-apis", + surfaces: [ + "openclaw package root `loadSessionStore`", + "openclaw package root `saveSessionStore`", ], + diagnostics: ["package-root session-store deprecation"], + tests: ["src/index.test.ts", "src/plugins/compat/registry.test.ts"], releaseNote: - "The beta.5 session-store import set and package-root whole-store aliases remain available while official plugins and package consumers migrate to row-level session access.", + "Package-root whole-store aliases retain their separate doctor upgrade condition and original review date.", }, { code: "removed-session-transcript-file-api", diff --git a/src/plugins/compat/registry.test.ts b/src/plugins/compat/registry.test.ts index d1cfec9297..e3d08503fe 100644 --- a/src/plugins/compat/registry.test.ts +++ b/src/plugins/compat/registry.test.ts @@ -183,20 +183,43 @@ describe("plugin compatibility registry", () => { "openclaw/plugin-sdk/security-runtime", ]), ); - expect(records.get("deprecated-session-store-beta5-api")?.surfaces).toEqual( - expect.arrayContaining([ - "openclaw package root loadSessionStore", - "openclaw package root saveSessionStore", - ]), + }); + + it("keeps session-store removals pending their distinct support conditions", () => { + const records = new Map(listPluginCompatRecords().map((record) => [record.code, record])); + const sdk = records.get("deprecated-session-store-beta5-api"); + const packageRoot = records.get("deprecated-package-root-session-store-api"); + + for (const record of [sdk, packageRoot]) { + expect(record).toMatchObject({ + status: "removal-pending", + deprecated: "2026-07-12", + warningStarts: "2026-07-12", + removeAfter: "2026-10-12", + }); + } + for (const record of [sdk, packageRoot]) { + expect(record?.replacement).toContain("published-reader migration"); + expect(record?.replacement).toContain("explicit breaking-release approval"); + } + expect(sdk?.owner).toBe("sdk"); + expect(sdk?.replacement).toContain( + "minimum supported plugin exclusion of v2026.7.1-beta.5", ); - expect(records.get("deprecated-session-store-beta5-api")).toMatchObject({ - status: "removal-pending", - removeAfter: "2026-10-12", - replacement: expect.stringContaining("minimum supported plugin exclusion"), - }); - expect(records.get("deprecated-session-store-beta5-api")?.replacement).toContain( - "legacy doctor upgrade window", + expect(sdk?.surfaces).toEqual([ + "openclaw/plugin-sdk/session-store-runtime loadSessionStore", + "openclaw/plugin-sdk/session-store-runtime updateSessionStore", + "openclaw/plugin-sdk/session-store-runtime resolveSessionFilePath", + "openclaw/plugin-sdk/session-store-runtime resolveSessionStoreEntry", + ]); + expect(packageRoot?.owner).toBe("core"); + expect(packageRoot?.replacement).toContain( + "the v2026.7.x upgrade window no longer requires the legacy doctor importer", ); + expect(packageRoot?.surfaces).toEqual([ + "openclaw package root `loadSessionStore`", + "openclaw package root `saveSessionStore`", + ]); }); it("keeps the removed embedded Pi aliases as a migration tombstone", () => { diff --git a/test/scripts/plugin-boundary-report.test.ts b/test/scripts/plugin-boundary-report.test.ts index d119556f6f..5abc1d8124 100644 --- a/test/scripts/plugin-boundary-report.test.ts +++ b/test/scripts/plugin-boundary-report.test.ts @@ -64,7 +64,7 @@ describe("plugin-boundary-report", () => { expect(summaryResult.exitCode).toBe(0); expect(summaryResult.stderr).toBe(""); expect(summary.compat?.eligibleForRemovalCount).toBe(0); - expect(summary.compat?.removalPendingCount).toBe(25); + expect(summary.compat?.removalPendingCount).toBe(26); expect(summary.compat?.removalPendingDueCount).toEqual(expect.any(Number)); expect(summary.compat?.removalPending?.map((record) => record.code)).toEqual([ "sdk-untrusted-context-identifier-aliases", @@ -90,6 +90,7 @@ describe("plugin-boundary-report", () => { "memory-host-compatibility-aliases", "plugin-sdk-broad-runtime-barrels", "plugin-sdk-focused-compat-aliases", + "deprecated-package-root-session-store-api", "deprecated-session-store-beta5-api", "plugin-sdk-plugin-config-runtime-public-demotion", ]); @@ -120,62 +121,98 @@ describe("plugin-boundary-report", () => { expect(result.exitCode).toBe(0); expect(result.stderr).toBe(""); - expect(result.stdout).toContain("removalPending=25"); + expect(result.stdout).toContain("removalPending=26"); 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); }); - it("reports the elapsed conditional beta.5 window and its outstanding blockers", () => { - vi.setSystemTime(new Date("2026-10-13T00:00:00Z")); - try { - const result = createPluginBoundaryReport([ - "--summary", - "--json", - "--fail-on-eligible-compat", - ]); - const summary = JSON.parse(result.stdout) as { - compat: { - removalPending: Array<{ - code: string; - removeAfter?: string; - dueForReview: boolean; - blocker: string; - readerCount: number; - }>; - }; - }; - const pending = summary.compat.removalPending.find( - (record) => record.code === "deprecated-session-store-beta5-api", - ); - - expect(result.exitCode).toBe(0); - expect(result.stderr).toBe(""); - expect(pending).toMatchObject({ removeAfter: "2026-10-12", dueForReview: true }); - expect(pending?.blocker).toContain("minimum supported plugin exclusion"); - expect(pending?.blocker).toContain("legacy doctor upgrade window"); - expect(pending?.blocker).toContain("explicit breaking-release approval"); - expect(pending?.readerCount).toBeGreaterThan(0); - } finally { - vi.setSystemTime(new Date("2026-10-09T00:00:00Z")); - } - }); + it.each(["2026-10-11", "2026-10-12", "2026-10-24"])( + "keeps conditional session-store removals visible without failing CI on %s", + (today) => { + vi.setSystemTime(new Date(`${today}T00:00:00Z`)); + try { + for (const mode of [[], ["--summary"]]) { + const result = createPluginBoundaryReport([ + ...mode, + "--json", + "--fail-on-cross-owner", + "--fail-on-unclassified-unused-reserved", + "--fail-on-eligible-compat", + ]); + const report = JSON.parse(result.stdout) as { + compat: { + eligibleForRemovalCount: number; + removalPending: Array<{ + code: string; + removeAfter: string; + blocker: string; + dueForReview: boolean; + }>; + }; + }; + expect(result.exitCode).toBe(0); + expect(result.stderr).toBe(""); + expect(report.compat.eligibleForRemovalCount).toBe(0); + const pending = report.compat.removalPending.filter((record) => + [ + "deprecated-session-store-beta5-api", + "deprecated-package-root-session-store-api", + ].includes(record.code), + ); + expect(pending).toEqual([ + expect.objectContaining({ + code: "deprecated-package-root-session-store-api", + blocker: expect.stringContaining("legacy doctor importer"), + removeAfter: "2026-10-12", + dueForReview: today >= "2026-10-12", + }), + expect.objectContaining({ + code: "deprecated-session-store-beta5-api", + blocker: expect.stringContaining("minimum supported plugin exclusion"), + removeAfter: "2026-10-12", + dueForReview: today >= "2026-10-12", + }), + ]); + for (const record of pending) { + expect(record.blocker).toContain("published-reader migration"); + expect(record.blocker).toContain("explicit breaking-release approval"); + } + if (mode.length === 0) { + expect(pending[0]).toHaveProperty( + "readerFiles", + expect.arrayContaining(["src/library.ts", "src/index.ts"]), + ); + } + } + } finally { + vi.setSystemTime(new Date("2026-10-09T00:00:00Z")); + } + }, + ); - it("still fails CI when an unconditional deprecated fixture window expires", () => { - const records = compatRegistry.listPluginCompatRecords(); - const conditional = records.find( - (record) => record.code === "deprecated-session-store-beta5-api", - ); - if (!conditional) { - throw new Error("Expected the retained beta.5 compatibility record"); - } - const unconditional: (typeof records)[number] = { ...conditional, status: "deprecated" }; - // Exercise the unchanged unconditional gate independently of the retained - // production contract, whose published-reader conditions remain unverified. - const registry = vi - .spyOn(compatRegistry, "listPluginCompatRecords") - .mockReturnValue(records.map((record) => (record === conditional ? unconditional : record))); - vi.setSystemTime(new Date("2026-10-13T00:00:00Z")); + it.each([ + ["2026-10-11", 0], + ["2026-10-12", 1], + ["2026-10-24", 1], + ])("still enforces an unconditional removal date on %s", (today, expectedExit) => { + const registry = vi.spyOn(compatRegistry, "listPluginCompatRecords").mockReturnValue([ + { + code: "deprecated-session-store-beta5-api", + status: "deprecated", + owner: "sdk", + introduced: "2026-05-21", + deprecated: "2026-07-12", + warningStarts: "2026-07-12", + removeAfter: "2026-10-12", + replacement: "Use the replacement API; unconditional removal approved", + docsPath: "/plugins/compatibility", + surfaces: ["test fixture unconditional SDK API"], + diagnostics: ["test fixture deprecation"], + tests: ["test/scripts/plugin-boundary-report.test.ts"], + }, + ]); + vi.setSystemTime(new Date(`${today}T00:00:00Z`)); try { const result = createPluginBoundaryReport([ "--summary", @@ -183,15 +220,18 @@ describe("plugin-boundary-report", () => { "--fail-on-eligible-compat", ]); const summary = JSON.parse(result.stdout) as { - compat: { eligibleForRemoval: Array<{ code: string }>; removalPendingCount: number }; + compat: { eligibleForRemoval: Array<{ code: string }> }; }; - expect(result.exitCode).toBe(1); - expect(result.stderr).toContain("1 compatibility record(s) are due for removal"); - expect(summary.compat.eligibleForRemoval.map((record) => record.code)).toEqual([ - "deprecated-session-store-beta5-api", - ]); - expect(summary.compat.removalPendingCount).toBe(24); + expect(result.exitCode).toBe(expectedExit); + expect(result.stderr).toBe( + expectedExit === 1 + ? "plugin-boundary-report: 1 compatibility record(s) are due for removal\n" + : "", + ); + expect(summary.compat.eligibleForRemoval.map((record) => record.code)).toEqual( + expectedExit === 1 ? ["deprecated-session-store-beta5-api"] : [], + ); } finally { registry.mockRestore(); vi.setSystemTime(new Date("2026-10-09T00:00:00Z")); From 56e4d440cb48e09c5d361b652a37c88d10873699 Mon Sep 17 00:00:00 2001 From: devSejung Date: Sat, 24 Oct 2026 21:08:18 +0900 Subject: [PATCH 2/2] style: format session-store retirement assertion --- src/plugins/compat/registry.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/plugins/compat/registry.test.ts b/src/plugins/compat/registry.test.ts index e3d08503fe..00a9431315 100644 --- a/src/plugins/compat/registry.test.ts +++ b/src/plugins/compat/registry.test.ts @@ -203,9 +203,7 @@ describe("plugin compatibility registry", () => { expect(record?.replacement).toContain("explicit breaking-release approval"); } expect(sdk?.owner).toBe("sdk"); - expect(sdk?.replacement).toContain( - "minimum supported plugin exclusion of v2026.7.1-beta.5", - ); + expect(sdk?.replacement).toContain("minimum supported plugin exclusion of v2026.7.1-beta.5"); expect(sdk?.surfaces).toEqual([ "openclaw/plugin-sdk/session-store-runtime loadSessionStore", "openclaw/plugin-sdk/session-store-runtime updateSessionStore",