docs/sync-and-op-log/package-boundaries.md
Status: Active Last Updated: May 13, 2026
This note documents the package split used by the operation-log sync stack. The goal is to keep reusable sync logic framework-agnostic while leaving Super Productivity domain wiring in the app.
Allowed direction:
src/app
-> @sp/sync-providers
-> @sp/sync-core
src/app
-> @sp/sync-core
packages/shared-schema
-> @sp/sync-core
Rules:
@sp/sync-core must not import Angular, NgRx, src/app, @sp/shared-schema,
@sp/sync-providers, or provider-specific code.@sp/sync-providers may import only public @sp/sync-core exports. It must
not deep-import @sp/sync-core/*, Angular, NgRx, src/app, or
@sp/shared-schema.packages/shared-schema depends on @sp/sync-core only for compatibility
re-exports of generic vector-clock algorithms.The packages/shared-schema -> @sp/sync-core edge is deliberate compatibility
coupling. Vector-clock compare/merge/prune algorithms moved to @sp/sync-core
so sync-core, client wrappers, and server/shared consumers use one
implementation. packages/shared-schema re-exports those algorithms to preserve
legacy import paths while consumers migrate; do not add new sync-engine logic to
@sp/shared-schema, and remove the compatibility edge once no consumers need
it.
@sp/sync-core owns reusable sync-engine primitives:
SyncLogger interface.@sp/sync-providers owns bundled provider implementations and provider-neutral
contracts:
sync-data.json and
file-sync version keys;Cross-provider utilities belong in @sp/sync-providers when they are reusable
by provider implementations but not by the generic engine. Existing examples are
provider-shared error classes, PKCE, retry predicates, native-HTTP retry, and
safe log-metadata helpers.
New bundled providers should follow the Dropbox/WebDAV/SuperSync/LocalFile
pattern: put provider-owned protocol logic and provider-neutral contracts in
@sp/sync-providers, then compose app-only credentials, platform bridges,
validators, OAuth routing, and UI config in thin app-side factories. If a
provider is app-specific or plugin-provided rather than bundled, implement it
app-side against the provider contracts instead of widening the package surface.
src/app owns host-specific configuration and choreography:
ActionType, ENTITY_TYPES, SyncProviderId, provider lists, and storage
prefixes such as REMOTE_FILE_CONTENT_PREFIX and PRIVATE_CFG_PREFIX;@sp/shared-schema;packages/shared-schema owns Super Productivity schema contracts and validators
that are shared between app and server. In this boundary it should stay
SP-coupled and should not become a dependency of @sp/sync-core or
@sp/sync-providers.
Package consumers should import from package barrels only:
import { compareVectorClocks } from '@sp/sync-core';
import { Dropbox, PROVIDER_ID_DROPBOX } from '@sp/sync-providers/dropbox';
Do not import from package internals such as @sp/sync-core/src/*,
@sp/sync-providers/src/*, or dist/*. If a host needs a symbol, promote it to
the package barrel deliberately and check that it is not app-owned.
The root @sp/sync-providers barrel has been removed; consumers MUST import
from focused subpath barrels: @sp/sync-providers/dropbox, /webdav,
/super-sync, /local-file, /http, /errors, /file-based, /pkce,
/platform, /provider-types, /credential-store, and /log. Provider
classes, provider-owned string constants, and shared privacy-boundary logging
helpers are exported there, but app enums such as SyncProviderId are not.
Internal helpers such as WebDAV API/adapter classes stay unexported unless a
second host needs them.
@sp/sync-core still exports deprecated full-state op compatibility defaults
and host-defined OpType.SyncImport / BackupImport / Repair strings for
existing consumers. New reusable hosts should provide their own full-state
operation strings through createFullStateOpTypeHelpers().
Package logging must use SyncLogger and safe structured metadata only.
SyncLogger is a privacy-aware port shape; it does not sanitize arbitrary
metadata. Call sites are responsible for passing already-scrubbed values, and
enforcement is currently code review plus focused tests.
IDs, counts, action strings, entity types, provider IDs, and error names/codes are acceptable. URL metadata is acceptable only after the caller strips query strings, fragments, credentials, tokens, raw response bodies, and user-provided path segments such as file names, emails, share IDs, or folder names. Prefer coarse path templates, provider operation names, host-only values, or a provider-owned relative path category over raw URL paths.
Full entities, operation payloads, task titles, note text, raw provider
responses, credentials, headers, and encryption material must stay out of
exportable logs. A lint rule for unsafe direct logging remains a possible
follow-up; until then, new movable/provider code should use SyncLogger and
tests should assert privacy-sensitive catch paths.
The ESLint package-boundary overrides apply to all TypeScript files under
packages/sync-core/** and packages/sync-providers/**, including tests. Tests
may import their own package internals through relative paths for white-box
coverage. @sp/sync-providers tests may import public @sp/sync-core exports,
but should not import @sp/sync-core internals or sync-core test helpers.
Before moving code across these boundaries, run:
npm run lint
npm run sync-core:build
npm run sync-providers:build
npm run packages:test
For a quick boundary spot-check, use:
rg -n "from ['\"](@angular|@ngrx|@sp/shared-schema|src/app|@sp/sync-core/src|@sp/sync-core/)|import\(['\"](@angular|@ngrx|@sp/shared-schema|src/app|@sp/sync-core/src|@sp/sync-core/)" packages/sync-core/src packages/sync-providers/src