Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Sync Submit Boundary

## Problem

Asset execution bypassed the synchronization reducer through public runner methods. Callers received staging paths, cache handles and leases directly, splitting completion identity and stale-resource cleanup between adapters and business state owners.

## Decision

`Core.step` is the only public producer of `runner_effect`. The effect variant is private and its execution tickets are abstract. Callers can inspect, retain and submit an issued effect, but cannot reconstruct it, including with a ticket from another effect or through the visible compiled module alias.

The public execution entry point is `Effect_runner.submit`. Dependency construction, `create` and `shutdown` remain lifecycle interfaces. Public asset execution, scoped callback execution, authentication execution and crypto execution helpers were removed. The cache implementation lives in the existing private Bootstrap module; the former public Asset_cache module exposes no execution values. No Dune change was needed.

Asset execution follows `Asset_requested -> step -> Asset_io -> submit -> Asset_completion -> step -> Asset_finished` or reducer-issued cleanup. The request binds an operation, exact graph scope and action metadata; the opaque ticket also binds the captured execution context. Asset bytes never enter reducer state, events or business outputs. Downloads, PUT, staging, cache inspection, retain/release, path delivery, pruning, scope closure, deletion, retry waits and cancellation use this route. Protected graph-value crypto uses the same public event/completion/output boundary with a distinct ticket and request type.

The state owners remain separate:

- Worker Asset_upload owns durable intent, local attachment insertion, metadata mutation, publication acknowledgement, recovery and cancellation. Staging and intent persistence still precede graph mutation and PUT. A successful PUT does not complete import publication.
- Sync Core owns asset execution admission, pending identity, cancellation, completion acceptance and resource cleanup effects. Graph sync policy and Asset_transfer demand policy retain their existing independent reducers.
- Runner owns network, crypto, file and cache I/O plus execution resource budgets. It returns typed completions and does not mutate reducer state or run business retry loops.

Cancelled pending tickets remain eligible for late resource cleanup. Duplicate and mismatched completions cannot publish another operation's success. Deletion cancellation matches the actual persistent origin/user/graph target across presentation and graph generations. Cleanup submitted before or after shutdown executes through submit; shutdown cannot retire queued cleanup without deleting its resource. Worker waiter reclamation covers cancelled callers and outputs already delivered but not yet accepted.

Staging returns an independent physical `UUID.nonce.type` identity. A late completion cannot delete a replacement import using the same business UUID. Recovery pruning preserves exact durable intent file identities. Legacy `UUID.type` staging remains recoverable. Only durable save and required cleanup are protected from cancellation; staging completion waits are cancellable and their lock is released in finally.

The existing download/upload/codec limits (3/1/1) and shared 64 MiB byte reservation remain. Partial reservation cancellation releases acquired capacity without poisoning the acquisition gate. Reservations exceeding the shared budget fail before asset bytes are read. Local store construction permits smaller budgets without increasing production defaults.

Staging reconciliation builds a hash index of exact durable filenames once, avoiding a historical-intent scan for every directory entry. Runner replay protection weakly tracks the identity of reducer-issued private effects: a caller-held effect remains protected across GC, while an unreachable effect cannot be reconstructed through the public interface and its replay entry is reclaimable. Historical scope strings and completed requests are not retained for the runner lifetime. This changes runner execution bookkeeping only, without adding public constructors or changing reducer state ownership.

## Verification

- PR52 automated review identified staging membership complexity and permanent replay bookkeeping. The latter is a runner-owned allocation defect that cannot be reproduced through pure Core alone. A public runner regression reproduces 154888 retained live words over 8192 completed requests before the fix and passes after it; the existing lease-replay regression also passes across a full major collection. All 22 effect-runner cases pass. Existing exact-file staging reconciliation coverage validates the membership change without duplicating policy tests. Read-only independent review found no concrete regression in weak identity lookup, held-capability GC behavior or filename indexing. The full repository build and forced tests passed again after these changes: 21 Alcotest suites, 593 named cases and the remaining assertion/script checks, including source-boundary validation. An initial sandboxed full-suite attempt denied localhost peer binding and was stopped; permission evidence was preserved and the approved rerun exited 0. Initial harness syntax and liveness corrections, the failing allocation evidence and final results remain ignored in `docs/test-reports/pr52-*`. The first PR CI run on `49b05d8` also passed; that is evidence for its earlier head only.
- PR integration was revalidated after rebasing onto final main `6810edcce353c04449e4e27262b26b1c9ea431ed` (PR50/51). Both full CI build and forced repository tests exited 0 on integrated head `ef62c92ede89fdd4c4cd50c63336624c334d8c70`: 21 Alcotest suites and 592 named cases, plus the remaining assertion/script regressions. Source-boundary and both installation manifests passed. The 9 native caller cases and Apple/upstream interoperability were rerun successfully; production adapter RSS was 112427008 bytes, allocation 86698576 bytes, and native interop RSS 130105344 bytes. The old 588-case result below remains evidence for its frozen implementation only. Shared pin commits were dropped from this PR; final main's token control lane, soft-scroll code and two-manifest pin contract were retained. Independent integration review found no new concrete bug. Existing tests separately cover control-lane saturation and actual worker/Db asset cancellation; current Service DI cannot selectively hold an asset completion while keeping its control coordinator running, so no synthetic combined test or new test API was added. New evidence is ignored under `docs/test-reports/rebased-*`.
- Follow-up CI-equivalent repository validation on frozen implementation `dd9488bd74f5cac47cf4495e008578d698852c70` passed: `dune build @all app/native_embed.exe.o` and `dune runtest --force` both exited 0. The full run includes 588 named Alcotest cases across 21 suites plus the remaining assertion/script regressions. Source-boundary checks, including both generated installation manifests, passed. No further interface migration omissions were found. The existing installed switch and worktree build were reused; no Dune or product changes were needed for this follow-up. Evidence is ignored under `docs/test-reports/repository-build-29.log` and `repository-runtest-30.log`.
- `dune runtest logseq_sync logseq_db_worker` passed: sync main suite 182, worker runner 16, worker Core 14, Asset_upload 11, Asset_transfer 12, asset protocol 4, codec 4, descriptor 3, and public negative compilation 1. Existing protocol, overlay and LUI package checks completed under their aliases.
- The public compile harness uses only exported spec interfaces through the existing test entry point. It rejects private constructor reconstruction, ticket forgery, visible Core alias construction, removed runner APIs and public/private Bootstrap access; holding and submitting effects compiles.
- Ownership regressions were reproduced before fixes: cross-generation deletion completion acceptance, replacement staging identity and queued graph I/O entering authentication after deletion. Pure acceptance defects are tested only through Core.step; filesystem and scheduler defects use the narrow runner boundary. Worker tests include cancellation of queued/resolved resource outputs and actual Db shutdown with physical staging deletion.
- Compiled native mutation fixtures passed all 9 cases through the migrated direct caller.
- Upstream Logseq/Apple crypto interoperability passed in both directions for 0, 1, 256, 4097, 131057 and 8388608 bytes. The production 8 MiB adapter allocated 86698576 OCaml bytes and peaked at 114049024 RSS bytes; native interoperability peaked at 130252800 RSS bytes, within existing limits.
- Failures and final evidence are retained only in ignored test-report directories. Tests used synthetic accounts, temporary files and localhost peers; no user graph, account or phone was tested.
- Full decision-document checks remain blocked by baseline documents: `implemented/feature/2026-09-28-bottom-lui-capsules.md` lacks Problem, Alternatives considered and Consequences; final main's `implemented/bugfix/2026-10-06-timeline-soft-scroll-edge.md` lacks Alternatives considered and Consequences. This decision is validated separately.

## Alternatives considered

### Compatibility wrappers and merged ownership

Compatibility wrappers were explicitly rejected because they preserve bypass paths. Moving durable import policy into sync or hiding business retry waits in the runner was rejected because those states have different owners. Stable per-operation staging filenames were rejected after the late-completion regression reproduced resource aliasing.

## Consequences

This is a breaking interface cutover: direct consumers must request operations through events and accept reducer outputs. Effect opacity is enforced without adding modules or changing Dune. Existing cache files and durable legacy staging remain usable. The isolated worktree reuses the installed toolchain and avoids a duplicate Journal/LUI application build. A separately reviewed shared compatibility commit fixes the repository LUI manifests to the validated revision; global opam state is unchanged and this does not claim compatibility with latest LUI.
Loading
Loading