From 3fca0f3c4b2c7b6cf63c92989b391522bc8ce1cd Mon Sep 17 00:00:00 2001 From: "pulse-triage[bot]" <249995291+pulse-triage[bot]@users.noreply.github.com> Date: Sun, 25 Oct 2026 06:19:07 +0000 Subject: [PATCH 1/4] Bind configuration transfers to their confirmed workspace Capture the target before file or CSRF preparation, freeze submitted inputs and use the existing strict non-retrying API path. Keep one operation pending, suppress late downloads/reloads after scope changes or teardown, and require explicit resubmission after token entry. An unconfirmed import may have applied: retain that warning rather than claiming no work occurred. Browser reset controls show transport-level replay despite one application fetch; server-side single-application protection remains a separate Core assessment. Change-source: pulse-maintainer --- .../subsystems/frontend-primitives.md | 32 ++ .../v6/internal/subsystems/registry.json | 19 +- .../493917fb380a8068.json | 57 +++ .../Settings/BackupTransferDialogs.tsx | 44 ++- .../Settings/RecoverySettingsPanel.tsx | 4 +- .../src/components/Settings/Settings.tsx | 3 + .../components/Settings/SettingsDialogs.tsx | 6 + .../__tests__/BackupTransferDialogs.test.tsx | 29 +- .../__tests__/settingsArchitecture.test.ts | 7 + .../__tests__/useBackupTransferFlow.test.ts | 335 +++++++++++++++++ .../Settings/useBackupTransferFlow.ts | 345 ++++++++++-------- 11 files changed, 714 insertions(+), 167 deletions(-) create mode 100644 frontend-modern/browser-verification/493917fb380a8068.json create mode 100644 frontend-modern/src/components/Settings/__tests__/useBackupTransferFlow.test.ts diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 26b668b576..55f3afc747 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -1864,6 +1864,9 @@ removal/failure retention and containing publication remain separate evidence. 142. `frontend-modern/src/components/Settings/__tests__/settingsLocalization.test.ts` 143. `frontend-modern/src/i18n/__tests__/i18n.test.ts` +- `frontend-modern/src/components/Settings/BackupTransferDialogs.tsx` +- `frontend-modern/src/components/Settings/useBackupTransferFlow.ts` + ## Shared Boundaries ### Transport-independent operator rows @@ -9997,3 +10000,32 @@ transcript block moved to `frontend-modern/src/components/patrol/` with its `CopyValueButton` composition intact. The External agents settings page lives at `/settings/pulse-intelligence/agents`; the old `/assistant` path redirects there. + +### Configuration transfers retain their confirmed workspace + +Recovery's export/import dialogs capture and display the effective workspace ID +when opened, including explicit `default` scope. Their production flow freezes +inputs before asynchronous file reads, binds both POSTs through the API client's +`expectedOrgID` and `retry: false`, and rejects a changed workspace before dispatch +(including the client's post-CSRF recheck). An invalid organisation must never +fall back to default, and a CSRF refusal must not replay a transfer. Only one +transfer can be pending; controls and dismissal remain disabled while waiting. + +Token entry saves a credential without submitting either transfer. The operator +must review the target and submit explicitly, including after a server refusal. +A failed/unknown import response is not proof that nothing applied: persistent +in-dialog guidance requires checking the destination before another attempt. +Exports discard late data after a workspace change; successful imports identify +the original workspace and never reload a different one. Leaving Settings aborts +the client's wait and suppresses late downloads, notices and reload timers; it +does not promise to undo a server-side import or prevent transport-level replay. +A non-retrying application fetch is not server-side single-application protection. +Existing archive formats, +authorisation, scope and encrypted payloads remain unchanged. + +`useBackupTransferFlow.test.ts` runs the real API client across delayed file/CSRF +preparation, scope switches, duplicate calls, non-retryable failures, token entry, +unmount, archive compatibility and cleanup. `BackupTransferDialogs.test.tsx` +checks target/error disclosure and pending input/action states. Browser evidence +must exercise the production flow and consuming dialogs at desktop and narrow +widths with safe responses, not a replacement transfer implementation. diff --git a/docs/release-control/v6/internal/subsystems/registry.json b/docs/release-control/v6/internal/subsystems/registry.json index b1193fc2f1..9d1969074c 100644 --- a/docs/release-control/v6/internal/subsystems/registry.json +++ b/docs/release-control/v6/internal/subsystems/registry.json @@ -5575,7 +5575,9 @@ "frontend-modern/src/utils/updatesPresentation.ts", "frontend-modern/src/utils/upgradeNavigation.ts", "frontend-modern/src/utils/workloadEmptyStatePresentation.ts", - "frontend-modern/src/utils/workloadGuestPresentation.ts" + "frontend-modern/src/utils/workloadGuestPresentation.ts", + "frontend-modern/src/components/Settings/BackupTransferDialogs.tsx", + "frontend-modern/src/components/Settings/useBackupTransferFlow.ts" ], "verification": { "allow_same_subsystem_tests": false, @@ -5583,6 +5585,21 @@ "exact_files": [], "require_explicit_path_policy_coverage": true, "path_policies": [ + { + "id": "settings-configuration-transfer", + "label": "configuration transfer target and dispatch proof", + "match_prefixes": [], + "match_files": [ + "frontend-modern/src/components/Settings/BackupTransferDialogs.tsx", + "frontend-modern/src/components/Settings/useBackupTransferFlow.ts" + ], + "allow_same_subsystem_tests": false, + "test_prefixes": [], + "exact_files": [ + "frontend-modern/src/components/Settings/__tests__/BackupTransferDialogs.test.tsx", + "frontend-modern/src/components/Settings/__tests__/useBackupTransferFlow.test.ts" + ] + }, { "id": "settings-update-install-guide", "label": "settings update install guide proof", diff --git a/frontend-modern/browser-verification/493917fb380a8068.json b/frontend-modern/browser-verification/493917fb380a8068.json new file mode 100644 index 0000000000..65f7fc2198 --- /dev/null +++ b/frontend-modern/browser-verification/493917fb380a8068.json @@ -0,0 +1,57 @@ +{ + "version": 1, + "base_sha": "d051c39bca60ba8cfbda0d07a07b74c228bc7eae", + "verified_at": "2026-10-25T06:19:00Z", + "result": "passed", + "changed_paths": [ + "frontend-modern/src/components/Settings/BackupTransferDialogs.tsx", + "frontend-modern/src/components/Settings/RecoverySettingsPanel.tsx", + "frontend-modern/src/components/Settings/Settings.tsx", + "frontend-modern/src/components/Settings/SettingsDialogs.tsx", + "frontend-modern/src/components/Settings/useBackupTransferFlow.ts" + ], + "content_sha256": { + "frontend-modern/src/components/Settings/BackupTransferDialogs.tsx": "48021e33b8243aa08e964bfa8d00dd458d862542290717bb1819cada34fb8bc1", + "frontend-modern/src/components/Settings/RecoverySettingsPanel.tsx": "5326391538b6dd5abdb795753e645c32483a24947f91c2edaf03cdbfd28dd9db", + "frontend-modern/src/components/Settings/Settings.tsx": "fc11be5e7072f7c6e2c3e1004ead39aeb760aa9df689dc9f818dad1fdd0f4fcf", + "frontend-modern/src/components/Settings/SettingsDialogs.tsx": "3369aaaef68ef2fb84c448674a5b93dd8058994d4c35db44b09033123a4b2582", + "frontend-modern/src/components/Settings/useBackupTransferFlow.ts": "e114bdad70b09349aee656b36a0abd1ad9b80ce1a4343fb9333f6d664ed9b76d" + }, + "routes": [ + "/settings/system/recovery (offline fixture mounting production RecoverySettingsPanel, useBackupTransferFlow and SettingsDialogs/BackupTransferDialogs with real apiClient, Dialog, ToastContainer and CSS; not full app/auth)" + ], + "states": [ + "Workspace target retained from dialog open; changed scope before submit and during file read refuses dispatch; late export discarded; successful old-workspace import cannot reload the new selection.", + "One pending operation disables inputs, actions and dismissal; immutable file/passphrase sent to captured workspace.", + "HTTP invalid_org, CSRF and rate-limit responses make one application fetch and one server request, no default fallback or false success.", + "Token preflight and server-token-error prompts save a credential without submitting; deliberate manual submission remains usable.", + "Transport reset: one application fetch produces three same-workspace wire requests in each viewport; import remains unconfirmed and may have applied. This is not single-application protection.", + "Unmount during file read suppresses dispatch; populated export yields one byte-exact synthetic encrypted-envelope download at each viewport." + ], + "interactions": [ + "Desktop keyboard focus/Enter and narrow touch open the existing recovery dialogs; select file, enter masked passphrase and submit.", + "Delay original server responses and file reads; switch selected organisation via the real apiClient setter; assert captured scope and late-response suppression.", + "Inspect visible target, pending and persistent uncertain guidance; save a synthetic API token and explicitly submit, never auto-submit.", + "85 browser checks, 24 server request observations, two byte-exact downloads; all ten settled desktop/narrow captures inspected with native view_image." + ], + "viewports": [ + { + "width": 1440, + "height": 1000, + "browser": "Chromium", + "browser_version": "141.0.7390.37" + }, + { + "width": 320, + "height": 940, + "browser": "Chromium", + "browser_version": "141.0.7390.37", + "touch": true + } + ], + "evidence": "Current lane OUTCOME/RECEIPTS.md, browser-result.json, PIXEL-REVIEW.md and BROWSER-BINDING.json; Playwright 1.56.1", + "limitations": [ + "Safe synthetic responses exercise the real frontend producer/consumer, not backend auth, archive cryptography, full app shell, private runtime, native recovery or release delivery.", + "Application retry opt-out is not server-side idempotency; Core assessment/repair retains the observed transport replay gap." + ] +} diff --git a/frontend-modern/src/components/Settings/BackupTransferDialogs.tsx b/frontend-modern/src/components/Settings/BackupTransferDialogs.tsx index 1fb03b3fb9..5fc646d7b3 100644 --- a/frontend-modern/src/components/Settings/BackupTransferDialogs.tsx +++ b/frontend-modern/src/components/Settings/BackupTransferDialogs.tsx @@ -21,6 +21,9 @@ interface BackupTransferDialogsProps { showExportDialog: Accessor; showImportDialog: Accessor; showApiTokenModal: Accessor; + transferOrgID: Accessor; + isTransferring: Accessor; + transferError: Accessor; apiTokenInput: Accessor; setApiTokenInput: (value: string) => void; handleExport: () => void; @@ -43,6 +46,19 @@ export const BackupTransferDialogs: Component = (pro > +

+ Workspace ID: {props.transferOrgID()} +

+ + + + +

+ Exporting configuration. Keep this dialog open while Pulse waits for a response. +

+
@@ -51,6 +67,7 @@ export const BackupTransferDialogs: Component = (pro