Skip to content

test(config): harden client recovery paths - #427

Open
AprilNEA wants to merge 2 commits into
masterfrom
xuan/code-557
Open

test(config): harden client recovery paths#427
AprilNEA wants to merge 2 commits into
masterfrom
xuan/code-557

Conversation

@AprilNEA

@AprilNEA AprilNEA commented Aug 7, 2026

Copy link
Copy Markdown
Member

Summary

  • cover snapshot fetch faults and stale intermediary recovery without activating bad configuration
  • exercise mobile adapter composition across normal-channel outage, persisted emergency state, and explicit release
  • extend the real Electron pilot harness with independent normal/emergency HTTPS origins, reconstructed-runtime persistence, equal-version equivocation rejection, and sticky release

Verification

  • pnpm check:ci
  • pnpm test — 2,732 passed, 1 skipped
  • pnpm test packages/foundation/common/src/config/__tests__/core.test.ts apps/mobile/src/runtime/config/__tests__/adapters.test.ts — 40 passed
  • xvfb-run -a pnpm -F @linkcode/desktop e2e:config-canary
  • pnpm -F @linkcode/mobile smoke:export — Android and iOS Hermes bytecode, source maps, routes, and bundled config sentinel validated
  • pilot fixture: 10,238 bytes, SHA-256 54ce1fc855e12295a8dd1490463c9afac8e84a526f1e16340bcefe4f0fec8e39
  • emergency fixture: 14,540 bytes, SHA-256 2fa79670900ed6e80c159cd2a569814f6e5c0302059b7c5ffeb2c0a5707af3b4

Boundaries

Electron restart coverage uses separate real app launches over shared atomic files, not abrupt OS process-kill injection. Mobile restart coverage reconstructs the core over the adapter seam with the frozen desktop-target fixture; the Hermes export is an app-entry bundle gate, not device/provider/native-module execution.

HQ counterpart: https://github.com/arcboxlabs/linkcodehq/pull/40

@linear-code

linear-code Bot commented Aug 7, 2026

Copy link
Copy Markdown

CODE-557

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Three of the six new Electron scenarios assert a transient refresh status that races the desktop app's own 1s background emergency refresh. As written they will fail nondeterministically depending on how fast the renderer paints.

Reviewed changes — full initial review of the two commits on xuan/code-557; test-only, no production source touched.

  • Normal-channel fault coverage (packages/foundation/common/src/config/__tests__/core.test.ts) — snapshot 404 / 200-with-no-body must still persist the advanced highWater and trusted pointer while leaving lkg unset, and a stale-intermediary pointer 200 must be rejected as replay with both in-memory state and the stored blob byte-identical. Traced both against ConfigCore.#refreshNormal; the assertions pin the behavior they claim.
  • Mobile emergency composition (apps/mobile/src/runtime/config/__tests__/adapters.test.ts) — drives ConfigCore over the real createConfigNetwork / createConfigStorage adapters through killSwitch(v1) → forcedMinimum(v2) → restart → release(v3) → restart-offline with the normal channel permanently down. The two restarts genuinely re-read MemoryAtomicStorage, so the persistence claim is real coverage.
  • Second HTTPS origin for the pilot E2E (apps/desktop/e2e/config-canary/{dist-server,electron-app,fixture,state-file}.mts) — startEmergencyServer on EMERGENCY_PORT = PORT + 1000 (port ranges are disjoint), readEmergencyState split out behind a storageKey parameter, and the bootstrap's emergencyEndpoint switched from null to a live origin.
  • Six new Electron scenarios (apps/desktop/e2e/config-canary.e2e.mts) — kill switch during main-channel outage, newer document replacing it, survival across a reconstructed runtime, explicit release, equal-version equivocation rejection, and sticky release.

I checked whether enabling emergencyEndpoint perturbs the 12 pre-existing scenarios: it does not. emergencyMode starts at 'offline', so those launches just destroy the socket, the refresh errors, and the retry backs off to 60s; no pre-existing assertion reads emergencySupport or the emergency request log.

ℹ️ The E2E carrying these scenarios never runs in CI

e2e:config-canary appears only in apps/desktop/package.json:21 — no workflow in .github/workflows/ invokes it, unlike e2e:unpackaged, e2e:packaged, e2e:window-bounds, e2e:startup, and e2e:browser. Combined with the up-to-15-minute MAIN_VITE_CONFIG_BOOTSTRAP rebuild it requires, that means the emergency scenarios will realistically be exercised only when someone runs them by hand, which is also what makes the race above expensive: it will surface on an unrelated future run with no known-good baseline to compare against. Whether to gate this in CI (nightly, or on changes under src/config/) is your call, not something the diff can decide.

Technical details
# `e2e:config-canary` is manual-only

## Affected sites
- `apps/desktop/package.json:21` — the only reference to `e2e:config-canary` in the repo.
- `.github/workflows/ci.yml:129,166,180,186,215` — the E2E suites that *are* gated; config-canary is absent.

## Required outcome
- A deliberate decision on whether the emergency-recovery scenarios are CI-enforced or
  documented as a manual pre-release gate. Either is defensible; silently manual is the
  outcome to avoid.

## Open questions for the human
- Is the ~15 min desktop rebuild the blocker? If so, a nightly or `paths`-filtered job on
  `packages/foundation/common/src/config/**` + `apps/desktop/src/main/config*.ts` would cover
  the regression surface without taxing every PR.

ℹ️ Nitpicks

  • apps/desktop/e2e/config-canary.e2e.mtsharness.emergency.requests.length = 0 is reset in all five new scenarios but read in only one (line 226). Either assert on the log (see the equivocation comment) or drop the unused resets.
  • apps/desktop/e2e/config-canary.e2e.mtsemergencyMode is assigned outside withLaunch while mode is assigned inside it. Threading the emergency mode through withLaunch alongside nextMode would make the reset impossible to forget.
  • apps/desktop/e2e/config-canary/dist-server.mts:177 — the emergency server hardcodes /v1/acme/desktop/emergency.json, whereas startDistServer derives every path from the fixture (artifact.step.pointerPath). Deriving it from fixture.target.brandId keeps the two servers consistent if the fixture's brand ever changes.
  • apps/mobile/src/runtime/config/__tests__/adapters.test.ts:92 — the it.each(['ios','android']) parameterization is inert for this case. os only reaches context, and context is read solely by evaluateSnapshot, which never runs here because the normal channel is rejectingFetch. Both parameterizations execute identical code; a plain it would say the same thing in half the time.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

Comment on lines +216 to +217
assert.equal(boundary.report.normal, 'error');
assert.equal(boundary.report.emergency, 'updated');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This asserts a transient refresh status that races the app's own background emergency refresh. startDesktopConfigRefresh() runs at app-ready before createDesktopWindow() (apps/desktop/src/main/index.ts:75) and schedules an emergency refresh 1s later (FIRST_EMERGENCY_REFRESH_DELAY_MS, apps/desktop/src/main/config.ts:38), while launchApp only waits for body to be visible. If that background refresh lands first it accepts the document and stores the ETag, so this explicit refresh() sends If-None-Match, the new server 304s (dist-server.mts:186), and report.emergency is 'not-modified'. Same exposure at lines 237 and 262.

Comment on lines +276 to +282
assert.equal(boundary.report.emergency, 'error');
const emergency = boundary.info.emergency;
assert(emergency);
assert.deepEqual(emergency.disabledFeatures, []);
assert.equal(emergency.emergencyVersion, '3');
assert.equal((await readEmergencyState(harness.home))?.raw, releaseRaw);
console.log('PASS equal-version emergency equivocation cannot replace explicit release');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This scenario passes without the equivocating document ever being delivered — report.emergency === 'error' and unchanged persisted bytes are equally consistent with a 404, a path typo, or a dead server, which is exactly what the two 'offline' scenarios around it assert. Since anti-equivocation is the security property being claimed, add the delivery proof the kill-switch scenario already uses at line 226: await waitForRequest(harness.emergency, (request) => request.mode === 'equivocation' && request.status === 200);. The requests.length = 0 reset on line 273 is already set up for it.

@pullfrog

pullfrog Bot commented Aug 7, 2026

Copy link
Copy Markdown

Your Claude subscription has hit its usage limit. It resets at 11am (UTC). Re-trigger Pullfrog after the reset, or add an ANTHROPIC_API_KEY repo secret — Pullfrog routes around an exhausted subscription automatically when one is present.

Add repo secret → · Model settings → · Setup docs → · Ask in Discord →

Pullfrog  | Rerun failed job ➔View workflow run | via Pullfrog | Using Claude Opus𝕏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant