Files
buzz/desktop/tests/e2e/observer-archive-policy.spec.ts
Will Pfleger 5179726737 fix(local-archive): default both archive settings to enabled (#4750)
## Overview

Both local archive settings — "Archive my agents' observer frames" (kind
24200) and "Archive my agents' turn metrics" (kind 44200) — previously
defaulted to OFF in OSS builds, controlled by build-time env vars. This
had an irreversible cost: observer frames are ephemeral (not stored by
the relay), so any missed events are permanently unrecoverable. This PR
makes both settings default to enabled for all builds and removes the
build-time flag machinery entirely.

## What changed

### Rust

- `observer_archive_default_enabled()` — returns `true` unconditionally;
removed `option_env!("BUZZ_DESKTOP_BUILD_OBSERVER_ARCHIVE_DEFAULT")`
check and `nest_is_dev()` runtime fallback.
- `agent_metric_archive_default_enabled()` — returns `true`
unconditionally; removed
`option_env!("BUZZ_DESKTOP_BUILD_AGENT_METRIC_ARCHIVE_DEFAULT")` check
and its OSS-build test.
- `build.rs` — removed both `rerun-if-env-changed` declarations
(`BUZZ_BUILD_OBSERVER_ARCHIVE_DEFAULT`,
`BUZZ_BUILD_AGENT_METRIC_ARCHIVE_DEFAULT`) and the two baked-env
emitting blocks.

### Build / CI

- `Justfile` — removed `desktop-tauri-test-compiled-flags` recipe (the
dual-compile test machinery).
- `.github/workflows/ci.yml` — removed the "Desktop Tauri compiled-flag
verification" CI step.

### TypeScript

- `useObserverArchiveSeed.ts` — removed `observerArchiveDefaultEnabled`
dep from `ObserverArchiveSeedDeps` and the `policyOn` gate in
`reconcileObserverArchive`; the function now unconditionally calls
`mergeSaveSubscriptionKinds`.
- `useAgentMetricArchiveSeed.ts` — removed
`agentMetricArchiveDefaultEnabled` dep from `AgentMetricArchiveSeedDeps`
and the `defaultOn` flag-check path in `maybeSeed`; the
`hasExplicitChoice` guard is preserved as the sole gate against
re-seeding.
- `LocalArchiveSettingsCard.tsx` — removed `policy` prop,
`observerPolicy` state, and `observerArchiveDefaultEnabled` fetch from
`ObserverArchiveSection`; toggle is now always enabled (just `toggling`
disables it); removed the stale "Always on for internal builds" copy
branch; removed the `observerPolicy !== false` guard from
`handleObserverToggle`.
- `tauriArchive.ts` — updated JSDoc on both default-enabled functions to
reflect always-true.
- `e2eBridge.ts` — changed both mock defaults from `?? false` to `??
true` so E2E tests without an explicit mock override exercise the real
default behavior.

### Tests

- `useObserverArchiveSeed.test.mjs` — replaced `policyOn` dep with
direct merge dep; updated `test_oss_policy_off_no_merge` →
`test_reconcile_always_seeds_24200`; all cancellation, identity-switch,
and ordering tests adapted.
- `useAgentMetricArchiveSeed.test.mjs` — removed `defaultOn` dep and
`test_oss_build_does_not_seed`; updated
`test_internal_build_unset_seeds_*` → `test_default_enabled_*`;
`hasExplicitChoice` guard tests unchanged.

## Preservation of explicit opt-outs

Users who have previously toggled the setting off are unaffected:

- `useAgentMetricArchiveSeed` skips seeding when
`hasExplicitChoice(pubkey)` returns true (localStorage-persisted per
identity).
- Observer archive reconciliation now unconditionally calls
`mergeSaveSubscriptionKinds`, but a user who already deleted the
subscription can turn it off via the Settings toggle, which calls
`removeSaveSubscriptionKind` — this is the existing explicit opt-out
path, and the toggle is now always enabled (not locked by a policy
flag).

## Result

- No `BUZZ_BUILD_*_ARCHIVE_DEFAULT` /
`BUZZ_DESKTOP_BUILD_*_ARCHIVE_DEFAULT` references remain outside
CHANGELOG/history.
- Desktop node tests: 4168 pass, 0 fail.
- `just desktop-tauri-check`: clean.
- `just desktop-tauri-test`: all pass.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-08-04 16:02:34 -04:00

226 lines
7.4 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { installMockBridge } from "../helpers/bridge";
async function openLocalArchiveSettings(page: import("@playwright/test").Page) {
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByTestId("open-settings").click();
await page.getByTestId("profile-popover-settings").click();
await expect(page.getByTestId("settings-view")).toBeVisible();
await page.getByTestId("settings-nav-local-archive").click();
const card = page.getByTestId("settings-local-archive");
await expect(card).toBeVisible({ timeout: 10_000 });
return card;
}
test.describe("observer archive policy — Settings toggle", () => {
test("fresh identity: observer toggle is enabled and checked by default", async ({
page,
}) => {
// Archive is default-on for all builds. A fresh identity (no stored opt-out)
// should show the toggle enabled and checked after reconciliation seeds the
// kind-24200 subscription.
await installMockBridge(page, {
saveSubscriptions: [
{
scope_type: "owner_p",
scope_value: "deadbeef".repeat(8),
kinds: "[24200]",
},
],
});
const card = await openLocalArchiveSettings(page);
const toggle = card.getByTestId("local-archive-observer-toggle");
await expect(toggle).toBeVisible({ timeout: 5_000 });
await expect(toggle).toBeEnabled();
await expect(toggle).toBeChecked();
});
test("toggle click OFF disables, then ON re-enables", async ({ page }) => {
await installMockBridge(page, {
saveSubscriptions: [
{
scope_type: "owner_p",
scope_value: "deadbeef".repeat(8),
kinds: "[24200]",
},
],
});
const card = await openLocalArchiveSettings(page);
const toggle = card.getByTestId("local-archive-observer-toggle");
await expect(toggle).toBeVisible({ timeout: 5_000 });
await expect(toggle).toBeChecked();
// OFF: removes kind 24200.
await toggle.click();
await expect(toggle).not.toBeChecked();
// ON again: re-creates the row from empty.
await toggle.click();
await expect(toggle).toBeChecked();
});
test("explicit opt-out persists across reload: toggle stays OFF", async ({
page,
}) => {
// Simulate a user who previously clicked OFF: the identity-scoped opt-out
// is recorded in localStorage ("0") and the owner_p/24200 subscription row
// is absent. Reconciliation must honour the stored choice and leave the
// toggle unchecked (user can re-enable via the toggle).
const MOCK_PUBKEY = "deadbeef".repeat(8);
await page.addInitScript(
({ storageKey }) => {
window.localStorage.setItem(storageKey, "0");
},
{
storageKey: `buzz:observer-archive-default-seeded:${MOCK_PUBKEY}`,
},
);
await installMockBridge(page, {
saveSubscriptions: [],
});
const card = await openLocalArchiveSettings(page);
const toggle = card.getByTestId("local-archive-observer-toggle");
await expect(toggle).toBeVisible({ timeout: 5_000 });
await expect(toggle).toBeEnabled();
await expect(toggle).not.toBeChecked();
});
test("no subscriptions, no stored choice: defaults ON then OFF removes, ON re-creates", async ({
page,
}) => {
// A fresh identity with no stored choice and an empty subscription table
// must be seeded to ON by reconciliation. Thereafter the toggle must
// function: OFF removes kind 24200, ON re-creates it.
await installMockBridge(page, {
saveSubscriptions: [],
});
const card = await openLocalArchiveSettings(page);
const toggle = card.getByTestId("local-archive-observer-toggle");
await expect(toggle).toBeVisible({ timeout: 5_000 });
// Default-on: reconciliation seeds the row, toggle must be checked.
await expect(toggle).toBeChecked();
// OFF: removes kind 24200.
await toggle.click();
await expect(toggle).not.toBeChecked();
// ON again: re-creates the row from empty.
await toggle.click();
await expect(toggle).toBeChecked();
});
});
test.describe("observer archive policy — reconciliation gate", () => {
test("archive sync reaches subscription path after reconciliation", async ({
page,
}) => {
// The reconciliation gate (useObserverArchiveReconciliation) must resolve
// successfully for a fresh identity, allowing useArchiveSync to start the
// ArchiveSyncManager, which calls list_save_subscriptions.
await installMockBridge(page, {
saveSubscriptions: [
{
scope_type: "owner_p",
scope_value: "deadbeef".repeat(8),
kinds: "[24200]",
},
],
});
await page.goto("/", { waitUntil: "domcontentloaded" });
// Wait for the channel list (proves AppShell mounted fully).
await expect(page.getByTestId("channel-general")).toBeVisible({
timeout: 10_000,
});
// The IPC counter proves the subscription path was reached.
await page.waitForFunction(
() => {
const counters = (window as Record<string, unknown>)
.__BUZZ_E2E_IPC_COUNTERS__ as Record<string, number> | undefined;
return (counters?.list_save_subscriptions ?? 0) > 0;
},
null,
{ timeout: 10_000 },
);
const count = await page.evaluate(() => {
const counters = (window as Record<string, unknown>)
.__BUZZ_E2E_IPC_COUNTERS__ as Record<string, number> | undefined;
return counters?.list_save_subscriptions ?? 0;
});
expect(count).toBeGreaterThan(0);
// The reconciliation gate must also result in a real `#p` + kind-24200
// live REQ filter.
const hasOwnerKindSubscription = await page.evaluate(
(ownerPubkey) =>
(
window as Window & {
__BUZZ_E2E_HAS_MOCK_OWNER_KIND_SUBSCRIPTION__?: (input: {
ownerPubkey: string;
kind: number;
}) => boolean;
}
).__BUZZ_E2E_HAS_MOCK_OWNER_KIND_SUBSCRIPTION__?.({
ownerPubkey,
kind: 24200,
}) ?? false,
"deadbeef".repeat(8),
);
expect(hasOwnerKindSubscription).toBe(true);
});
test("fresh install with empty subscriptions: reconciliation seeds kind 24200", async ({
page,
}) => {
// A fresh install with no owner_p/24200 row must end up with one after
// startup reconciliation runs — the actual production repair path.
await installMockBridge(page, {
saveSubscriptions: [],
});
await page.goto("/", { waitUntil: "domcontentloaded" });
await expect(page.getByTestId("channel-general")).toBeVisible({
timeout: 10_000,
});
await expect
.poll(
() =>
page.evaluate(
(ownerPubkey) =>
(
window as Window & {
__BUZZ_E2E_HAS_MOCK_OWNER_KIND_SUBSCRIPTION__?: (input: {
ownerPubkey: string;
kind: number;
}) => boolean;
}
).__BUZZ_E2E_HAS_MOCK_OWNER_KIND_SUBSCRIPTION__?.({
ownerPubkey,
kind: 24200,
}) ?? false,
"deadbeef".repeat(8),
),
{ timeout: 10_000 },
)
.toBe(true);
const commands = await page.evaluate(
() =>
(window as Window & { __BUZZ_E2E_COMMANDS__?: string[] })
.__BUZZ_E2E_COMMANDS__ ?? [],
);
expect(commands).toContain("merge_save_subscription_kinds");
});
});