From 362796e0e4a3877798d699b6d1f2f72e9f71ff8a Mon Sep 17 00:00:00 2001 From: rcmerci Date: Thu, 22 Oct 2026 17:14:03 +0800 Subject: [PATCH] Simplify sync operation resets and Journal adapters --- app/application.ml | 3 +- app/journal_asset_import.ml | 29 ++-- app/journal_graph_runtime.ml | 36 +---- app/journal_graph_runtime.mli | 11 +- .../2026-10-22-payload-free-graph-ready.md | 56 +++++++ .../2026-10-22-reuse-attachment-decoding.md | 55 +++++++ .../2026-10-22-shared-sync-operation-reset.md | 58 +++++++ logseq_sync/lib/pure_reducer/core.ml | 152 ++++-------------- 8 files changed, 215 insertions(+), 185 deletions(-) create mode 100644 docs/agent-guide/implemented/simplification/2026-10-22-payload-free-graph-ready.md create mode 100644 docs/agent-guide/implemented/simplification/2026-10-22-reuse-attachment-decoding.md create mode 100644 docs/agent-guide/implemented/simplification/2026-10-22-shared-sync-operation-reset.md diff --git a/app/application.ml b/app/application.ml index c15acd00..37472d22 100644 --- a/app/application.ml +++ b/app/application.ml @@ -1095,8 +1095,7 @@ let apply_worker_response state (response : Journal_graph_runtime.response) = if next == state || stale then next else report_ui_error next ~operation:"listFavorites" ~phase:"read" | Favorites_invalidated -> favorites_event state Invalidate - | Journal_graph_runtime.Graph_ready info -> - ignore info.admission_facts; + | Journal_graph_runtime.Graph_ready -> let state = resolve_worker_errors state in { state with write_enabled = true; graph_ready = true; graph_error = None } | Admission_inspected _ | Admission_unavailable _ -> state diff --git a/app/journal_asset_import.ml b/app/journal_asset_import.ml index 45dbf7ea..82a76ca9 100644 --- a/app/journal_asset_import.ml +++ b/app/journal_asset_import.ml @@ -87,9 +87,8 @@ let staged_of_pick (pick : Json.pick) = ; source_identity = pick.sourceIdentity } ;; -let parse payload = +let picked (wire : Json.event) = let ( let* ) = Result.bind in - let* wire = read_event payload in match wire.action, wire.selection with | "picked", Some selection -> let* staged = staged_of_pick selection in @@ -97,23 +96,6 @@ let parse payload = | _ -> Error "Invalid attachment selection" ;; -let decode ~target payload = - Result.map - (fun ((pick : staged), _) -> - Logseq_db_types.Asset_import. - { operation = pick.operation - ; asset = pick.asset - ; target - ; replace_reference = None - ; local_mutation = pick.local_mutation - ; metadata_mutation = pick.metadata_mutation - ; source_file = pick.source_file - ; title = pick.title - ; file_type = pick.file_type - }) - (parse payload) -;; - let to_import (staged : staged) ~target : Logseq_db_types.Asset_import.t = { operation = staged.operation ; asset = staged.asset @@ -127,6 +109,13 @@ let to_import (staged : staged) ~target : Logseq_db_types.Asset_import.t = } ;; +let decode ~target payload = + let ( let* ) = Result.bind in + let* wire = read_event payload in + let* staged, _ = picked wire in + Ok (to_import staged ~target) +;; + type event = | Picked of staged * int option | Picked_batch of staged list * int option * string option @@ -140,7 +129,7 @@ let decode_event payload = let* wire = read_event payload in let request_id = Option.map (fun (request : Json.request) -> request.id) wire.request in match wire.action with - | "picked" -> Result.map (fun (staged, id) -> Picked (staged, id)) (parse payload) + | "picked" -> Result.map (fun (staged, id) -> Picked (staged, id)) (picked wire) | "picked-batch" -> (match wire.items with | None -> Error "Invalid attachment batch" diff --git a/app/journal_graph_runtime.ml b/app/journal_graph_runtime.ml index 3151dc21..cb8d0943 100644 --- a/app/journal_graph_runtime.ml +++ b/app/journal_graph_runtime.ml @@ -9,15 +9,6 @@ type worker_failure = ; error : Error.t } -type graph_info = - { graph_uuid : Graph.Uuid.t - ; graph_name : string - ; schema : Graph.schema_version - ; admission_facts : Graph.admission_fact list - ; generation : string - ; projection_revision : string - } - type failure_source = | Worker_failure of worker_failure | Projection_failure of string @@ -43,7 +34,7 @@ type payload = * Logseq_db_worker.Protocol.v2_favorites_result | Favorites_failed of Journal_graph_request.favorites_request * bool * string | Favorites_invalidated - | Graph_ready of graph_info + | Graph_ready | Admission_inspected of { request : Journal_graph_request.admission_request ; observation : Protocol.v2_admission_inspection @@ -2933,34 +2924,13 @@ let receive_response t (protocol_response : Protocol.response) = | Pull_changes { request_generation; _ } -> rehydrate_current_interests t ~request_generation ~generation | _ -> failure_output t operation request_id reason) - | V2_graph_info_outcome - { graph_uuid - ; graph_name - ; schema - ; admission_facts - ; journal_title_format - ; generation - ; projection_revision - ; _ - } -> + | V2_graph_info_outcome { journal_title_format; _ } -> (match operation with | Graph_info -> let catalog = if t.task_catalog_started then [] else [ request_task_catalog t ] in - { requests = catalog - ; responses = - [ response - (Graph_ready - { graph_uuid - ; graph_name - ; schema - ; admission_facts - ; generation - ; projection_revision - }) - ] - } + { requests = catalog; responses = [ response Graph_ready ] } | Capture_format { command; page_uuid; page_revision } -> let journal_day = Journal_time.local_day command.creation_time in requests diff --git a/app/journal_graph_runtime.mli b/app/journal_graph_runtime.mli index e6b41324..e5966a5d 100644 --- a/app/journal_graph_runtime.mli +++ b/app/journal_graph_runtime.mli @@ -4,15 +4,6 @@ type worker_failure = ; error : Logseq_db_worker.Error.t } -type graph_info = - { graph_uuid : Logseq_db_types.Graph_types.Uuid.t - ; graph_name : string - ; schema : Logseq_db_types.Graph_types.schema_version - ; admission_facts : Logseq_db_types.Graph_types.admission_fact list - ; generation : string - ; projection_revision : string - } - type failure_source = | Worker_failure of worker_failure | Projection_failure of string @@ -38,7 +29,7 @@ type payload = * Logseq_db_worker.Protocol.v2_favorites_result | Favorites_failed of Journal_graph_request.favorites_request * bool * string | Favorites_invalidated - | Graph_ready of graph_info + | Graph_ready | Admission_inspected of { request : Journal_graph_request.admission_request ; observation : Logseq_db_worker.Protocol.v2_admission_inspection diff --git a/docs/agent-guide/implemented/simplification/2026-10-22-payload-free-graph-ready.md b/docs/agent-guide/implemented/simplification/2026-10-22-payload-free-graph-ready.md new file mode 100644 index 00000000..85bef298 --- /dev/null +++ b/docs/agent-guide/implemented/simplification/2026-10-22-payload-free-graph-ready.md @@ -0,0 +1,56 @@ +# Payload Free Graph Ready + +## Problem + +At main b6c1c77c4cd7b94963e4729dfac55b825c838085, Journal_graph_runtime copies +six graph-info fields into Graph_ready. Its sole Application consumer ignores +the payload. The app library has no public_name; repository-wide searches found +no other Graph_ready consumer, registration or serialized representation. + +## Proposal + +Make the internal Graph_ready notification payload-free and remove its graph_info +record from app/journal_graph_runtime.ml and .mli. Preserve V2_graph_info requests, +correlation, failure handling, task-catalog launch, readiness publication and +independent admission inspection. Do not change worker protocol or its codecs. + +## Decision + +Adopt the proposal as explicitly authorized by the user on 2026-10-22. +Implementation and verification are local; no push or PR is authorized. + +## Alternatives considered + +### Remove the graph-info request + +Not selected: the successful response gates readiness and starts task-catalog +loading. Other graph-info operations consume journal formatting and revisions. + +## Acceptance criteria + +- Existing runtime-locality, Application and worker integration tests pass. +- Temporary public Runtime traces normalize only the intentionally removed + internal payload and compare requests/notifications across success, failure, + abandonment/reset, duplicate and stale completion paths. +- Independent admission reads and their tests remain intact. + +## Risks + +- Removing the internal payload must not remove the actual request or its + failure/correlation boundary. Supported external consumers discovered during + implementation would require reconsidering the scope. + +## Consequences + +Graph_ready is now an internal payload-free notification; worker protocol and +independent admission inspection are unchanged. Baseline and final runtime-locality +(152 cases), Application (144 cases, including admission refresh and unlock), and +worker integration (15 cases) pass. Temporary runtime replay yields 5,906 identical +records after normalizing only the removed Graph_ready argument, including explicit +success, duplicate, failure, abandonment, reset and stale-completion scenarios. +Native Swift startup/failure boundary tests also pass; no GUI or real graph was used. + +## Questions + +None. The user authorized this exact internal simplification on 2026-10-22; +no worker wire change, push or PR is authorized. diff --git a/docs/agent-guide/implemented/simplification/2026-10-22-reuse-attachment-decoding.md b/docs/agent-guide/implemented/simplification/2026-10-22-reuse-attachment-decoding.md new file mode 100644 index 00000000..5c7f410e --- /dev/null +++ b/docs/agent-guide/implemented/simplification/2026-10-22-reuse-attachment-decoding.md @@ -0,0 +1,55 @@ +# Reuse Attachment Decoding + +## Problem + +At main b6c1c77c4cd7b94963e4729dfac55b825c838085, decode_event parses the +event and its picked branch parses the same JSON again through parse. +decode also duplicates to_import's mapping from staged picks to worker imports. + +## Proposal + +Share the picked conversion on an already decoded Json.event and reuse +to_import from decode. Preserve all public entry points, typed JSON/UUID +validation, request identities, error strings, batch order and +replace_reference=None. Do not change Swift picking, cancellation, staging, +security scope, ownership or cleanup behavior. + +## Decision + +Adopt the proposal as explicitly authorized by the user on 2026-10-22. +Implementation and verification are local; no push or PR is authorized. + +## Alternatives considered + +### Merge the public APIs or narrow the event vocabulary + +Not selected: direct import, staged picks and batch events have existing +consumers and tests. Private reuse removes duplication without a protocol change. + +## Acceptance criteria + +- Existing routes/platform tests pass before and after the change. +- Temporary comparisons cover valid/invalid UUIDs, malformed JSON and typed + fields, absent/valid request IDs, all event kinds, ordered batches and exact + errors/import fields. Public APIs remain unchanged; temporary tests stay out + of the repository. + +## Risks + +- Reordering validation or using a broader event decoder could change errors or + accept previously invalid inputs. Reuse only the already validated record and + identical field mapping; do not claim a measured performance improvement. + +## Consequences + +The private picked helper consumes a validated event, and direct decode reuses +to_import. Existing routes/platform suites pass before and after. Seventy-four +temporary inputs produce identical events, import fields and error strings through +decode_event, decode and is_dismissal; staged/direct mapping assertions pass. +Cases cover typed-field errors, UUID validation, request IDs, controls, ordered +batches and malformed inputs. Swift source and lifecycle ownership are unchanged. + +## Questions + +None. The user authorized implementation and temporary before/after verification +on 2026-10-22, without permanent additional tests, push or PR. diff --git a/docs/agent-guide/implemented/simplification/2026-10-22-shared-sync-operation-reset.md b/docs/agent-guide/implemented/simplification/2026-10-22-shared-sync-operation-reset.md new file mode 100644 index 00000000..6240d1f9 --- /dev/null +++ b/docs/agent-guide/implemented/simplification/2026-10-22-shared-sync-operation-reset.md @@ -0,0 +1,58 @@ +# Shared Sync Operation Reset + +## Problem + +At main b6c1c77c4cd7b94963e4729dfac55b825c838085, six transitions in +logseq_sync/lib/pure_reducer/core.ml repeat the same nineteen operation-state +resets: authenticate_new_account, restore_local, sign_out, +select_graph_transition, clear_selected_graph_for_catalog and +return_to_graph_picker. Maintaining these identical assignments separately +obscures which resets are common and which lifecycle policies differ. + +## Proposal + +Extract only those identical assignments into a private pure helper. Keep all +caller-specific account/graph/presentation generations, pending-effect filtering, +mirror inspection, websocket ownership, cancellation, detach and publication +ordering at each caller. Do not apply the helper to fail, whose retained state +differs. No public interface, protected spec, Dune, dependency or wire changes. + +## Decision + +Adopt the proposal as explicitly authorized by the user on 2026-10-22. +Implementation and verification are local; no push or PR is authorized. + +## Alternatives considered + +### Generalize every teardown + +Not selected: failure, graph selection and account replacement have different +ownership and cancellation policies. A configurable reset framework would hide +those differences and add policy instead of sharing identical assignments. + +## Acceptance criteria + +- Existing pure core and recovery tests pass before and after the refactor. +- Temporary public-API traces compare inputs, visible state, admitted graph scope, + asset context and complete ordered effects for all six reset paths, including + stale completions after replacement. Evidence stays outside Git. +- No tests are removed, weakened or added permanently for the mechanical change. + +## Risks + +- Sharing a non-common reset would alter lifecycle behavior. Verify the exact + nineteen-field intersection and preserve every caller-specific assignment. + +## Consequences + +The six transitions now share one private nineteen-field reset. Every remaining +caller statement, including effect order, matches the baseline after extracting +those assignments; fail is byte-for-byte unchanged. Existing pure core (87 cases) +and recovery (21 cases) pass before and after. Temporary fixture replay covers +121 pure core/recovery/asset cases, with 1,972 identical public input/state/context/ +ordered-effect records across builds. No abstract state was inspected. + +## Questions + +None. On 2026-10-22 the user authorized these three audited simplifications and +actual before/after verification, with temporary evidence only and no push or PR. diff --git a/logseq_sync/lib/pure_reducer/core.ml b/logseq_sync/lib/pure_reducer/core.ml index a3748b8a..9e80bb1d 100644 --- a/logseq_sync/lib/pure_reducer/core.ml +++ b/logseq_sync/lib/pure_reducer/core.ml @@ -1175,6 +1175,32 @@ let save_cache core = next, [ Run request ] ;; +(* Only operation state shared by graph/account replacement paths. Scope, + pending-effect and teardown policies remain with each transition. *) +let reset_graph_operations core = + { core with + graph_key = None + ; pending_graph_open = None + ; pending_attachment = None + ; pending_encrypted_graph_key = None + ; pending_private_key_package = None + ; snapshot_scope = None + ; snapshot_server_t = None + ; active_snapshot_download = None + ; pending_snapshot_activation = None + ; sync_view = None + ; pending_sync_inspection = None + ; pending_outbox_transition = None + ; submission_owner = None + ; pull_owner = None + ; pull_wanted = None + ; pull_retry_delay = 1. + ; active_authoritative_batch = None + ; queued_authoritative_batch = None + ; deferred_authoritative_owner = None + } +;; + let authenticate_new_account core user_id = let startup = { initial_startup with @@ -1198,7 +1224,7 @@ let authenticate_new_account core user_id = in let core = set_snapshot - { core with + { (reset_graph_operations core) with user_id = Some user_id ; pending_effects = [] ; catalog_cache_loading = false @@ -1207,26 +1233,7 @@ let authenticate_new_account core user_id = ; cached_selected_graph = None ; current_graph_scope = None ; pending_selected_download = None - ; graph_key = None - ; pending_graph_open = None ; pending_mirror_inspection = None - ; pending_attachment = None - ; pending_encrypted_graph_key = None - ; pending_private_key_package = None - ; snapshot_scope = None - ; snapshot_server_t = None - ; active_snapshot_download = None - ; pending_snapshot_activation = None - ; sync_view = None - ; pending_sync_inspection = None - ; pending_outbox_transition = None - ; submission_owner = None - ; pull_owner = None - ; pull_wanted = None - ; pull_retry_delay = 1. - ; active_authoritative_batch = None - ; queued_authoritative_batch = None - ; deferred_authoritative_owner = None ; websocket_live = false } snapshot @@ -1285,7 +1292,7 @@ let restore_local core user_id = in let core = set_snapshot - { core with + { (reset_graph_operations core) with user_id = Some user_id ; pending_effects = [] ; catalog_cache_loading = true @@ -1295,26 +1302,7 @@ let restore_local core user_id = ; selected_graph_value = None ; current_graph_scope = None ; pending_selected_download = None - ; graph_key = None - ; pending_graph_open = None ; pending_mirror_inspection = None - ; pending_attachment = None - ; pending_encrypted_graph_key = None - ; pending_private_key_package = None - ; snapshot_scope = None - ; snapshot_server_t = None - ; active_snapshot_download = None - ; pending_snapshot_activation = None - ; sync_view = None - ; pending_sync_inspection = None - ; pending_outbox_transition = None - ; submission_owner = None - ; pull_owner = None - ; pull_wanted = None - ; pull_retry_delay = 1. - ; active_authoritative_batch = None - ; queued_authoritative_batch = None - ; deferred_authoritative_owner = None ; websocket_live = false } { core.public_state.snapshot with local_deletion = None; startup } @@ -1345,7 +1333,7 @@ let sign_out core = in let next = set_snapshot - { core with + { (reset_graph_operations core) with user_id = None ; pending_effects = [] ; catalog_cache_loading = false @@ -1354,26 +1342,7 @@ let sign_out core = ; cached_selected_graph = None ; current_graph_scope = None ; pending_selected_download = None - ; graph_key = None - ; pending_graph_open = None ; pending_mirror_inspection = None - ; pending_attachment = None - ; pending_encrypted_graph_key = None - ; pending_private_key_package = None - ; snapshot_scope = None - ; snapshot_server_t = None - ; active_snapshot_download = None - ; pending_snapshot_activation = None - ; sync_view = None - ; pending_sync_inspection = None - ; pending_outbox_transition = None - ; submission_owner = None - ; pull_owner = None - ; pull_wanted = None - ; pull_retry_delay = 1. - ; active_authoritative_batch = None - ; queued_authoritative_batch = None - ; deferred_authoritative_owner = None ; websocket_live = false } snapshot @@ -1440,32 +1409,13 @@ let select_graph_transition core ~persist graph_id = let mirror_request = { graph; scope } in let core = set_snapshot - { core with + { (reset_graph_operations core) with cached_selected_graph = Some graph_id ; pending_effects = List.filter (fun pending -> not (is_graph_key_access pending)) core.pending_effects ; selected_graph_value = Some graph ; current_graph_scope = Some scope ; pending_selected_download = (if persist then Some scope else None) - ; graph_key = None - ; pending_graph_open = None ; pending_mirror_inspection = Some mirror_request - ; pending_attachment = None - ; pending_encrypted_graph_key = None - ; pending_private_key_package = None - ; snapshot_scope = None - ; snapshot_server_t = None - ; active_snapshot_download = None - ; pending_snapshot_activation = None - ; sync_view = None - ; pending_sync_inspection = None - ; pending_outbox_transition = None - ; submission_owner = None - ; pull_owner = None - ; pull_wanted = None - ; pull_retry_delay = 1. - ; active_authoritative_batch = None - ; queued_authoritative_batch = None - ; deferred_authoritative_owner = None } snapshot in @@ -2432,32 +2382,13 @@ let clear_selected_graph_for_catalog core catalog = in let next = set_snapshot - { core with + { (reset_graph_operations core) with pending_effects = [] ; cached_selected_graph = None ; selected_graph_value = None ; current_graph_scope = None ; pending_selected_download = None - ; graph_key = None - ; pending_graph_open = None ; pending_mirror_inspection = None - ; pending_attachment = None - ; pending_encrypted_graph_key = None - ; pending_private_key_package = None - ; snapshot_scope = None - ; snapshot_server_t = None - ; active_snapshot_download = None - ; pending_snapshot_activation = None - ; sync_view = None - ; pending_sync_inspection = None - ; pending_outbox_transition = None - ; submission_owner = None - ; pull_owner = None - ; pull_wanted = None - ; pull_retry_delay = 1. - ; active_authoritative_batch = None - ; queued_authoritative_batch = None - ; deferred_authoritative_owner = None ; websocket_live = false } snapshot @@ -2841,32 +2772,13 @@ let return_to_graph_picker core = in let next = set_snapshot - { core with + { (reset_graph_operations core) with selected_graph_value = None ; current_graph_scope = None ; pending_selected_download = None - ; graph_key = None - ; pending_graph_open = None ; pending_mirror_inspection = None - ; pending_attachment = None - ; pending_encrypted_graph_key = None - ; pending_private_key_package = None - ; snapshot_scope = None - ; snapshot_server_t = None - ; active_snapshot_download = None - ; pending_snapshot_activation = None - ; sync_view = None ; pending_effects = [] - ; pending_sync_inspection = None - ; pending_outbox_transition = None ; websocket_live = false - ; submission_owner = None - ; pull_owner = None - ; pull_wanted = None - ; pull_retry_delay = 1. - ; active_authoritative_batch = None - ; queued_authoritative_batch = None - ; deferred_authoritative_owner = None } { core.public_state.snapshot with sync_phase = Offline