From df991466bb4a66b3d123add65e0fde84025aceed Mon Sep 17 00:00:00 2001 From: ggbdpq Date: Thu, 1 Oct 2026 01:14:39 +0800 Subject: [PATCH 1/2] fix(desktop): fold the usage fixture projection until no runs are pending MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit seedE2eFixture's settings-usage branch folded the appended model-call attempts with a single catchUpModelCallProjection() call. One pass bounds how many lagging runs it processes (default limit 16 in the model-call ledger), so a fixture that seeds more lagging runs than one pass's limit left the canonical usage read model incomplete: the page's first read saw only the first folded slice instead of every seeded call. Found while delivering the usage tab-entry measurement (#5804), whose padded fixture creates hundreds of lagging runs while a single pass folded only a dozen-ish. Fold in a loop until pendingRuns === 0 so the seed is complete regardless of fixture size. Production paths are untouched; this only changes the dev/test e2e fixture seeding. Verification: - new focused test (apps/desktop/src/main/__tests__/e2e-fixture-usage-fold.test.ts) red before the fix with a >16-run pad (expected 25 seeded model calls, the first read saw 16 — exactly one pass's limit) and green after (25/25 seen, pendingRuns 0); green on the unpadded tree as well - biome check on the touched files - npm run check:asf-headers, git diff --check, scripts/protocol-epoch-check.mjs --base 9b089f58e (no protocol changes) Refs #5804 Generated-by: GLM-5.3-Flash (ZCode) --- .../__tests__/e2e-fixture-usage-fold.test.ts | 73 +++++++++++++++++++ apps/desktop/src/main/e2e-fixture.ts | 7 +- 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 apps/desktop/src/main/__tests__/e2e-fixture-usage-fold.test.ts diff --git a/apps/desktop/src/main/__tests__/e2e-fixture-usage-fold.test.ts b/apps/desktop/src/main/__tests__/e2e-fixture-usage-fold.test.ts new file mode 100644 index 00000000000..b6e1a18ea94 --- /dev/null +++ b/apps/desktop/src/main/__tests__/e2e-fixture-usage-fold.test.ts @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { mkdtemp, rm } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { strict as assert } from 'node:assert'; +import { test } from 'node:test'; +import { resolveStorageRoot, tryAcquireInteractiveRootOwner } from '@maka/storage/root-authority'; +import { openInteractiveUsageStoresForWrite } from '@maka/storage/usage-stores'; +import { seedE2eFixture } from '../e2e-fixture.js'; +import { usageStatsRecords } from '../e2e-fixture/scenarios-usage.js'; +import { E2E_FIXTURE_NOW } from '../e2e-fixture/seed-helpers.js'; + +test('settings-usage fixture folds the canonical usage projection until no run is pending', async () => { + const workspaceRoot = await mkdtemp(join(tmpdir(), 'e2e-usage-fold-')); + try { + await seedE2eFixture({ + workspaceRoot, + fixture: { + scenario: 'settings-usage', + workspaceName: 'e2e-fixture-settings-usage', + reducedMotion: false, + theme: null, + locale: null, + timezone: null, + platform: null, + }, + now: E2E_FIXTURE_NOW, + }); + + const storageRoot = await resolveStorageRoot({ path: workspaceRoot, kind: 'interactive' }); + const owner = await tryAcquireInteractiveRootOwner(storageRoot); + assert.ok(owner, 'the fixture must release the storage root so readers can acquire it'); + const usage = await openInteractiveUsageStoresForWrite(owner.lease); + try { + // The page's first read must already see every seeded model call; the + // fixture folds the appended attempts into the read model before closing. + const expectedRequests = usageStatsRecords(E2E_FIXTURE_NOW).modelCalls.length; + const summary = await usage.modelCalls.modelCallSummary({ range: 'all' }, E2E_FIXTURE_NOW); + assert.equal( + summary.projection.totalRequests, + expectedRequests, + 'the first read must see every seeded model call', + ); + // One catch-up pass bounds how many lagging runs it folds, so a complete + // fold leaves nothing pending for later reads to repair. + const projection = await usage.modelCalls.catchUpModelCallProjection(); + assert.equal(projection.pendingRuns, 0, 'no lagging run may outlive the fixture seed'); + } finally { + await usage.close(); + await owner.close(); + } + } finally { + await rm(workspaceRoot, { recursive: true, force: true }); + } +}); diff --git a/apps/desktop/src/main/e2e-fixture.ts b/apps/desktop/src/main/e2e-fixture.ts index bf99d7975c0..9a9661efa8c 100644 --- a/apps/desktop/src/main/e2e-fixture.ts +++ b/apps/desktop/src/main/e2e-fixture.ts @@ -302,7 +302,12 @@ export async function seedE2eFixture(input: { await runStore.close?.(); // Fold the appended attempts into the read model so the page's first read // sees canonical usage (production's readCanonicalUsage also repairs). - await usage.modelCalls.catchUpModelCallProjection(); + // One pass bounds how many lagging runs it processes, and the padded + // fixture creates hundreds of runs, so fold until nothing is pending. + for (;;) { + const projection = await usage.modelCalls.catchUpModelCallProjection(); + if (projection.pendingRuns === 0) break; + } await usage.flush(); } finally { await usage.close(); From 2aff08621a595187041604cb48aa76594793fd09 Mon Sep 17 00:00:00 2001 From: ggbdpq Date: Thu, 1 Oct 2026 07:17:48 +0800 Subject: [PATCH 2/2] test(desktop): seed past the projection limit so the fold loop is actually guarded PR #5889 review (P3): the committed settings-usage fixture generates five model-call attempts, below catchUpModelCallProjection()'s default 16-run pass bound, so the fold test also passed with the previous single-pass implementation and did not guard the regression this PR fixes. Give seedE2eFixture a projectionCatchUpLimit test seam (fixture-side only; unset keeps the ledger default of 16 via positiveInteger's fallback) and cap each pass at 2 in the committed test: the five seeded runs then need three passes, so a reverted single-pass seed fails the first-read assertion instead of sneaking through. Verification: - red: fold loop temporarily reverted to one catchUpModelCallProjection() call with the seam applied - focused test failed with "the first read must see every seeded model call" 2 !== 5 (exit 1) - green: fold loop restored - focused test passes 1/0 (exit 0) - ablation: the test without the seam still passes against the fix (exit 0), i.e. the seam is what makes the test a guard; seam restored, final run green - biome check on the two touched files - npm run check:asf-headers, git diff --check, scripts/protocol-epoch-check.mjs --base c838e1fa7 (no protocol changes) Refs #5804 Generated-by: GLM-5.3-Flash (ZCode) --- .../src/main/__tests__/e2e-fixture-usage-fold.test.ts | 5 +++++ apps/desktop/src/main/e2e-fixture.ts | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/main/__tests__/e2e-fixture-usage-fold.test.ts b/apps/desktop/src/main/__tests__/e2e-fixture-usage-fold.test.ts index b6e1a18ea94..e76a385ecb9 100644 --- a/apps/desktop/src/main/__tests__/e2e-fixture-usage-fold.test.ts +++ b/apps/desktop/src/main/__tests__/e2e-fixture-usage-fold.test.ts @@ -43,6 +43,11 @@ test('settings-usage fixture folds the canonical usage projection until no run i platform: null, }, now: E2E_FIXTURE_NOW, + // The scenario seeds five model-call runs, under the ledger's default + // 16-run pass bound, so a single-pass seed would satisfy the assertions + // below. Capping each pass at 2 forces the fold to loop (2 + 2 + 1), which + // makes a reverted single-pass seed fail the first-read assertion. + projectionCatchUpLimit: 2, }); const storageRoot = await resolveStorageRoot({ path: workspaceRoot, kind: 'interactive' }); diff --git a/apps/desktop/src/main/e2e-fixture.ts b/apps/desktop/src/main/e2e-fixture.ts index 9a9661efa8c..c88a3365c6e 100644 --- a/apps/desktop/src/main/e2e-fixture.ts +++ b/apps/desktop/src/main/e2e-fixture.ts @@ -218,6 +218,8 @@ export async function seedE2eFixture(input: { workspaceRoot: string; fixture: E2eFixture; now?: number; + /** Test seam: bounds lagging runs per fold pass; unset keeps the ledger default. */ + projectionCatchUpLimit?: number; }): Promise { const now = input.now ?? E2E_FIXTURE_NOW; const scenario = input.fixture.scenario; @@ -305,7 +307,9 @@ export async function seedE2eFixture(input: { // One pass bounds how many lagging runs it processes, and the padded // fixture creates hundreds of runs, so fold until nothing is pending. for (;;) { - const projection = await usage.modelCalls.catchUpModelCallProjection(); + const projection = await usage.modelCalls.catchUpModelCallProjection({ + limit: input.projectionCatchUpLimit, + }); if (projection.pendingRuns === 0) break; } await usage.flush();