fix(plugins): preserve unavailable release failures

This commit is contained in:
2026-09-01 23:59:36 +08:00
parent 2c5b60ea78
commit 93e3143573
3 changed files with 165 additions and 0 deletions

View File

@@ -0,0 +1,135 @@
# Task: Diagnose Web Search runtime stale after reinstall
## Identity
- Task ID: 20260901-web-search-runtime-stale-r2-6f4a2d91
- Mode: Feature
- Branch: codex/20260901-web-search-runtime-stale-r2-6f4a2d91-web-search-runtime-stale-r2
- Worktree: D:\Datas\OthersProjects\makelore-web-search-runtime-stale-r2-6f4a2d91
- Base commit: 2c5b60ea78e014a712a47bdbb9efd156f52f3a1d
- Owner: codex-root
- Status: Ready for Integration
## Scope
- Diagnose the installed-client `plugin_runtime_stale` failure after reinstall and a
newly created Conversation, using the latest Pi session, installed artifact metadata,
Main log, frozen worker manifest, and server Admission response as evidence.
- Establish a deterministic read-only reproduction/inspection loop before reading or
changing product code. If the fault is client-owned, make the smallest correction in
the Marketplace/effective-resolver/hosted-admission/Web Search invocation seam with
focused regressions. If evidence locates a deployed server-state problem, stop client
mutation and report the exact server prerequisite instead.
- Preserve the user root worktree and its two unrelated untracked package records. All
product, test, and task-record writes remain in this isolated worktree.
## Intent And Constraints
- Concurrent Task Gate: Passed. Task `20260901-web-search-runtime-stale-r2-6f4a2d91`
owns this worktree/branch at exact base
`2c5b60ea78e014a712a47bdbb9efd156f52f3a1d`; the dirty root was not adopted,
stashed, reset, cleaned, or modified.
- Planning Gate: Passed after reading the project-memory startup set, current state,
decisions, architecture/module/data-flow, business rules, success criteria,
glossary, evidence/reflection/commitment/stale indexes, the prior assignment and
frozen-worker corrections, official bundled-plugin integration, Web Search main
merge, and the only active Web Search peer record.
- The old coordinator task `20260831-web-search-client-5b9d2e63` remains at base
`0a86ec825...` with no live ownership of this post-merge runtime failure. No semantic
or write-scope conflict was found.
- Confirmed product invariants: a new parent worker must freeze an acquired, enabled,
assigned, exact official bundled Release; child workers receive no hosted tool;
invocation still requires current account/project/policy and exact server-owned
Release Admission; Renderer/Pi must not receive credentials or Provider authority.
- The previous source correction permits only a verified frozen worker to survive a
transient stale Library projection. The user's fresh reinstall/new Conversation
disproves that correction as a complete explanation, so it will not be broadened
without new evidence.
- Do not restart or terminate the running client, invoke paid Web Search/OpenAI, alter
Account Library/project/Agent state, package, push, deploy, publish, or touch server
state without a separately evidenced need and authority.
## Project Context Loaded
- Project goal: keep Electron Main as the authenticated Marketplace/hosted-Plugin
authority while exposing only frozen, admitted parent tools to Pi.
- Current integrated focus: Web Search is an official bundled optional hosted Plugin;
assignment discovery and frozen-worker stale-Library fixes are already on `main`.
- Active task scope: isolate the remaining installed-client `plugin_runtime_stale`
boundary and correct only the proven owner.
- Decisions affecting this task: ADR-006 single Pi runtime and frozen logical-thread
ownership; distinct Library, project, Agent, runtime Admission, confirmation, and
billing states; no hot-swap and no child inheritance.
- Evidence/commitments: installed artifacts must be verified rather than inferred from
source; live paid Provider and production activation remain out of scope.
- Likely modules if client-owned: `electron/coding-plugins/registry.ts`,
`effective-resolver.ts`, `hosted-admission.ts`, `marketplace-client.ts`, Web Search
adapter, and their focused tests.
- Unknowns to resolve first: exact installed source/verification head; exact newest Pi
tool event and frozen manifest; whether failure occurs before or inside hosted
Admission; whether `/resolve` reports stale, mismatched request identity, or a
different exact Release.
- Gate result: Passed.
## Diagnosis Evidence
- The newest Pi session deterministically reproduces two non-billing
`plugin_runtime_stale` results for `makelore_web_search`; both stop before Provider
dispatch or Token Point reservation.
- The new worker manifest is current and correct: parent-only Web Search Skill/tool,
Release `00000000-0000-4000-8000-000000000204`, version `1.0.0`, contract v1,
exact project/account snapshot, and platform-metered policy are frozen.
- The installed `app.asar` is version `1.2.4` and contains the prior
`resolveForInvocation` fix, disproving missing installation, stale conversation,
missing Agent assignment, and missing prior product code.
- Live anonymous Marketplace detail returns `stable_release=null` and
`stable_version=null`. The server task history confirms `0077` lists metadata only;
`0078` creates the exact bundled stable Release and has not been deployed.
- The integrated `0078` migration additionally declares `min_makelore_version=2.0.0`,
while the complete compatible runtime is demonstrably present in installed
MakeLore `1.2.4`. Once `0078` is deployed this would become the next Admission
blocker.
- The remaining client-owned defect is diagnostic fidelity: an explicit Marketplace
`action=unavailable` result is currently folded into `plugin_runtime_stale` instead
of the existing typed `plugin_release_unavailable` failure.
## Outcome
- Corrected the hosted Admission boundary so an explicit Marketplace
`action='unavailable'` becomes the existing typed
`plugin_release_unavailable` failure instead of being folded into the unrelated
`plugin_runtime_stale` worker-snapshot error.
- Added regressions for both currently reachable unavailable reasons:
`plugin_release_not_ready` and `plugin_client_incompatible`.
- The fix is deliberately diagnostic, not a client-side bypass: exact frozen Release,
Account, project, policy, Admission, Provider, and billing validation remain intact.
The actual production prerequisite is the server's missing `0078` Release migration
followed by the `0079` compatibility correction.
## Verification
- Deterministic installed-client evidence: newest Pi session reproduced two
`plugin_runtime_stale` Web Search calls; the frozen parent manifest and installed
`1.2.4` app contained the expected Skill, tool, Release, account/project snapshot,
and prior resolver implementation.
- TDD RED: both explicit unavailable-reason cases returned
`plugin_runtime_stale`; GREEN after the boundary correction.
- `pnpm exec vitest run tests/unit/hosted-admission.test.ts
tests/unit/web-search-plugin-adapter.test.ts
tests/unit/game-resource-plugin-adapter.test.ts`: 3 files, 25 tests passed.
- `pnpm exec tsc --noEmit`: passed.
- Scoped ESLint for the changed implementation and test: passed.
- `git diff --check`: passed. No installed client, user root worktree, Account/Library,
project/Agent assignment, Provider, billing, or production state was mutated.
## Follow-ups
- Integrate this small diagnostic correction into the next MakeLore build so future
Release availability failures remain truthful. It is not required to make the
current installed runtime compatible once the server Release/migration gate is fixed.
- Deploy/apply server `0078` and `0079`, reopen the worker, then verify Provider
readiness before performing an explicitly authorized paid Web Search smoke.
## Promotion Candidates
- None recorded.

View File

@@ -192,6 +192,9 @@ export class MarketplaceHostedAdmissionResolver {
throw stale('Hosted Plugin worker admission is stale');
}
const item = resolved.items.find(({ pluginId: candidate }) => candidate === plugin);
if (item?.action === 'unavailable') {
throw unavailable('Hosted Plugin Release is unavailable');
}
const matches = item && (bundled
? matchesBundledFrozenRelease(item, bundled, plugin)
: matchesFrozenRelease(item, installed, plugin));

View File

@@ -122,6 +122,33 @@ describe('MarketplaceHostedAdmissionResolver', () => {
expect(error).toMatchObject({ code: 'plugin_runtime_stale', status: 409, retryable: false });
});
it.each([
'plugin_release_not_ready',
'plugin_client_incompatible',
])('preserves an explicit unavailable Release as plugin_release_unavailable (%s)', async (reason) => {
const marketplace = {
resolve: vi.fn(async () => resolved({
action: 'unavailable',
releaseAdmissionId: null,
reason,
})),
} as unknown as Pick<MarketplacePackageClientPort, 'resolve'>;
const unavailableResolver = new MarketplaceHostedAdmissionResolver({
packageStore: { getInstalledRelease: vi.fn(async () => installed()) } as unknown as Pick<PluginPackageStore, 'getInstalledRelease'>,
marketplace,
makeloreVersion: '1.2.4',
});
await expect(unavailableResolver.resolve({
pluginId: PLUGIN_ID,
workerSnapshot: { requestId: REQUEST_ID, pluginReleaseId: RELEASE_ID },
})).rejects.toMatchObject({
code: 'plugin_release_unavailable',
status: 409,
retryable: false,
});
});
it('projects an account change as a typed non-retryable worker failure', async () => {
const { resolver, resolve } = resolverFixture();
resolve.mockRejectedValueOnce({ code: 'marketplace_account_changed' });