Apply received changes again while remediation mode is active

Remediation mode prevents the reconciliation scan which readiness depends upon,
so the application never becomes ready while the modification-time limit is set.
Since the result processor began waiting for readiness, nothing was applied in
that mode at all, not even changes older than the limit, which is what the mode
exists for. Result application no longer requires readiness while the limit is
configured, but it does require a usable local database, because a change applied
while a fetch has reset the database cannot gather its chunks and is dropped.
The limit itself is still enforced for every change.

A scheduled fetch also no longer offers Simple Fetch in this mode. Simple Fetch
reconciles the Vault with the local database after fetching, past the check which
refuses that scan, so it could store the current files or write changes newer than
the limit. The detailed flow states the restriction and offers to clear it first.
This commit is contained in:
Kim Jansheden committed 2026-09-25 06:51:48 +02:00
1 parent 2c2b9c90e4
commit 01a83b13fb
6 files changed
+105 -3

No files matched your search

@@ -28,6 +28,8 @@ function note(id: string): PouchDB.Core.ExistingDocument<EntryDoc> {
type SetupOptions = {
applicationReady?: boolean;
databaseReady?: boolean;
maxMTimeForReflectEvents?: number;
processSynchroniseResult?: (entry: unknown) => Promise<boolean>;
setSnapshot?: (key: string, value: unknown) => Promise<unknown>;
};
@@ -41,6 +43,7 @@ function setup(options: SetupOptions = {}) {
const core = {
services: {
appLifecycle: { isReady, isSuspended: () => false },
database: { isDatabaseReady: () => options.databaseReady ?? true },
path: { getPath: (entry: { path: string }) => entry.path },
replication: {
databaseQueueCount: reactiveSource(0),
@@ -64,7 +67,10 @@ function setup(options: SetupOptions = {}) {
},
};
const processor = new ReplicateResultProcessor({
currentSettings: () => ({ maxMTimeForReflectEvents: 0, suspendParseReplicationResult: false }),
currentSettings: () => ({
maxMTimeForReflectEvents: options.maxMTimeForReflectEvents ?? 0,
suspendParseReplicationResult: false,
}),
getKeyValueDB: () => core.kvDB,
getLocalDatabase: () => core.localDatabase,
requestActiveReplicatorRetirement: () => {
@@ -124,6 +130,45 @@ describe("ReplicateResultProcessor", () => {
expect(isReady).toHaveBeenCalledOnce();
});
it("applies results in remediation mode, which never reports readiness", () => {
const { processor } = setup({
applicationReady: false,
maxMTimeForReflectEvents: Date.parse("2026-09-01T00:00:00Z"),
});
expect(processor.isSuspended).toBe(false);
});
it("holds results in remediation mode while the local database is being rebuilt", () => {
const { processor } = setup({
applicationReady: false,
databaseReady: false,
maxMTimeForReflectEvents: Date.parse("2026-09-01T00:00:00Z"),
});
expect(processor.isSuspended).toBe(true);
});
it("still skips a document modified after the limit while the application is unready", async () => {
const maxMTimeForReflectEvents = Date.parse("2026-09-01T00:00:00Z");
const { processor, processSynchroniseResult } = setup({
applicationReady: false,
maxMTimeForReflectEvents,
});
const tooRecent = {
...note("too-recent"),
mtime: maxMTimeForReflectEvents + 1,
} as PouchDB.Core.ExistingDocument<EntryDoc>;
processor.enqueueAll([tooRecent]);
await vi.waitFor(() => {
expect(processor["_queuedChanges"]).toHaveLength(0);
expect(processor["_processingChanges"]).toHaveLength(0);
});
expect(processSynchroniseResult).not.toHaveBeenCalled();
});
it("retires active ownership when a newer remote version is observed", async () => {
const { onCloseActiveReplication, processor } = setup();
const versionInfo = {