From 38f2358db3288c81462b2ac7f049ab737a29515d Mon Sep 17 00:00:00 2001 From: brother7 <7brother7@gmail.com> Date: Tue, 1 Sep 2026 12:17:31 +0800 Subject: [PATCH] fix(plugins): accept weak catalog etags --- ...901-plugin-catalog-load-client-7d4a8c21.md | 59 +++++++++++++++++++ electron/coding-plugins/marketplace-client.ts | 2 +- .../coding-plugin-marketplace-client.test.ts | 28 +++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 .project-docs/30-worklog/tasks/20260901-plugin-catalog-load-client-7d4a8c21.md diff --git a/.project-docs/30-worklog/tasks/20260901-plugin-catalog-load-client-7d4a8c21.md b/.project-docs/30-worklog/tasks/20260901-plugin-catalog-load-client-7d4a8c21.md new file mode 100644 index 0000000..ab5ea4d --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260901-plugin-catalog-load-client-7d4a8c21.md @@ -0,0 +1,59 @@ +# Task: Diagnose MakeLore Marketplace catalog loading + +## Identity + +- Task ID: 20260901-plugin-catalog-load-client-7d4a8c21 +- Mode: Feature +- Branch: codex/20260901-plugin-catalog-load-client-7d4a8c21-plugin-catalog-load-client +- Worktree: D:\Datas\OthersProjects\makelore-plugin-catalog-load-client-7d4a8c21 +- Base commit: 5e8b7266c2a8297c9d307b576256886659d44780 +- Owner: codex-root +- Status: Ready for Integration + +## Scope + +- Reproduce the installed MakeLore Marketplace catalog failure at the Renderer → IPC dispatcher → Main Marketplace client boundary. +- Verify the installed 1.2.0 artifact contains the current Marketplace composition and compare its parser/network behavior with the live catalog. +- If a client defect is confirmed, change only the smallest Marketplace client/Host API/error-projection seam and focused tests. + +## Intent And Constraints + +- Preserve the occupied client root worktree and its untracked packaging record; all product work stays in this isolated worktree. +- Do not expose Works credentials, read real `.env` files, alter Package Store/Provider/billing state, or publish a package. +- Treat local-package inference as forbidden: a failed server catalog must remain fail closed. +- The tight loop must reproduce the exact initial catalog failure or prove each boundary healthy before any implementation change. + +## Plan + +1. Verify the live catalog parses through the exact client DTO parser. +2. Inspect the running installed Main process, local Host API, logs, embedded bundle, and effective API origin. +3. Reproduce the failing boundary with a focused test/probe, rank falsifiable causes, and implement only the confirmed fix. +4. Run focused regressions and task documentation gates; do not rebuild/publish unless the fix affects packaged behavior and needs an artifact proof. + +## Outcome + +- Completed. The Marketplace client now accepts both strong and standards-compliant weak composite ETags, while preserving the exact received validator for the next `If-None-Match` request and retaining all generation/pricing identity checks. +- Root cause: the production gateway emits `W/"plugins-..."` for the compressed catalog response. The previous strong-only regular expression rejected that otherwise valid HTTP 200 response, and the local Host API surfaced the rejection as `plugin_backend_unavailable`. +- No fallback catalog, server mutation, proxy workaround, or billing/Provider behavior was added. + +## Verification + +- Live catalog parsed through `parseMarketplaceCatalogPage`: HTTP 200, total 3, exact IDs `makelore.data-service`, `makelore.game-resource`, `makelore.web-search`. +- Installed MakeLore is version 1.2.0 and its `app.asar` contains the Marketplace route, service composition, and production origin `https://square.nianxx.cn`. +- Running local Host API is on `127.0.0.1:13210`; an unauthenticated shell probe correctly returns 401 and therefore cannot yet distinguish dispatcher/service from upstream failure. +- Installed-session Electron probes returned HTTP 200 and the weak composite ETag; the exact source Marketplace client failed before the fix with `marketplace_response_invalid: invalid composite Marketplace ETag` and returned all three official Plugins after the fix. +- TDD regression: the new weak-ETag/304-reuse case failed before the product change and passed after it. +- `pnpm exec vitest run tests/unit/coding-plugin-marketplace-client.test.ts tests/unit/plugin-marketplace-routes.test.ts tests/unit/plugin-marketplace-store.test.ts tests/unit/plugin-marketplace-pages.test.tsx`: 4 files, 68 tests passed. +- `pnpm test`: 214 files, 1761 tests passed, 2 expected skips; pressure test 1/1 passed. +- `pnpm run typecheck`: passed. +- `pnpm run lint:check`: 0 errors and 5 unchanged warnings outside this task. +- `pnpm run build:vite`: Renderer, Main, Preload, and utility builds passed. +- Scoped ESLint and `git diff --check`: passed. + +## Follow-ups + +- Integrate this task into the client main branch, rebuild the Windows artifact, and install/restart it. The currently installed MakeLore 1.2.0 artifact cannot pick up this source-only fix dynamically. + +## Promotion Candidates + +- None recorded. diff --git a/electron/coding-plugins/marketplace-client.ts b/electron/coding-plugins/marketplace-client.ts index d9bf018..547f8fc 100644 --- a/electron/coding-plugins/marketplace-client.ts +++ b/electron/coding-plugins/marketplace-client.ts @@ -44,7 +44,7 @@ const PLUGIN_ID_PATTERN = /^[a-z][a-z0-9.-]{0,127}$/u; const SHA256_PATTERN = /^[a-f0-9]{64}$/u; const RELEASE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u; const REQUEST_ID_PATTERN = /^[\x21-\x7e]{1,128}$/u; -const ETAG_PATTERN = /^"plugins-(\d+)-tp-([A-Za-z0-9._-]{1,128})"$/u; +const ETAG_PATTERN = /^(?:W\/)?"plugins-(\d+)-tp-([A-Za-z0-9._-]{1,128})"$/u; type UnknownRecord = Record; type FetchImplementation = (input: string | URL, init?: RequestInit) => Promise; diff --git a/tests/unit/coding-plugin-marketplace-client.test.ts b/tests/unit/coding-plugin-marketplace-client.test.ts index 39d21e9..75931bc 100644 --- a/tests/unit/coding-plugin-marketplace-client.test.ts +++ b/tests/unit/coding-plugin-marketplace-client.test.ts @@ -350,6 +350,34 @@ describe('Marketplace client and account cache', () => { await expect(client.readCatalog({ limit: 10 })).resolves.toMatchObject({ stale: true, total: 1 }); }); + it('accepts a weak composite ETag from compressed Marketplace responses and reuses it verbatim', async () => { + const pricingVersionId = '00000000-0000-0000-0000-000000000420'; + const etag = `W/"plugins-2-tp-${pricingVersionId}"`; + const fetcher = vi.fn() + .mockResolvedValueOnce(response({ ...catalogPage, catalog_generation: 2 }, {}, { + ETag: etag, + 'X-Plugin-Catalog-Generation': '2', + 'X-Token-Point-Pricing-Version': pricingVersionId, + })) + .mockResolvedValueOnce(new Response(null, { status: 304 })); + const client = createMarketplaceClient({ + fetchImpl: fetcher, + apiBaseUrl: 'https://square.example', + getAccessToken: async () => null, + subscribeSession: () => () => undefined, + }); + + await expect(client.readCatalog({ limit: 10 })).resolves.toMatchObject({ + total: 1, + etag, + generation: 2, + pricingVersionId, + stale: false, + }); + await expect(client.readCatalog({ limit: 10 })).resolves.toMatchObject({ etag, stale: false }); + expect(fetcher.mock.calls[1]?.[1]?.headers).toMatchObject({ 'If-None-Match': etag }); + }); + it('refreshes download authentication at most once before accepting the artifact', async () => { const archive = Buffer.from('signed-artifact'); const { grant } = signedGrant(archive);