Merge main into NFC transport support

This commit is contained in:
vorotamoroz committed 2026-11-01 14:11:17 +00:00
commit 4957f8d28f
160 files changed
+12441 -1388

No files matched your search

+101
View File
@@ -0,0 +1,101 @@
---
date: 2026-11-01
commonlib-version: "0.2.2"
self-hosted-livesync-version: "1.0.43"
status: accepted
---
# CLI database continuity
Publication note: the correction for #1304 is implemented and locally validated,
but has not been released. The reviewed baseline is Self-hosted LiveSync `0e458e5e` with
Commonlib `0.2.2`. The [CLI guide](../../src/apps/cli/README.md) describes the
command interface; the [test procedure](../../test/README.md#cli-fetch-database-continuity)
describes the Object Storage reproduction.
## Required behaviour
A successful `fetch` or `rebuild` must leave subsequent CLI commands using the
reconstructed local database. The Commonlib Rebuilder selects the host app ID
as the database suffix. The CLI saves that selection on success even without
`--write-settings`; the option continues to govern other lasting settings
changes. Temporary suspension values remain runtime state.
Before reconstruction can mutate a database, the CLI records the operation,
database directory, Vault path, and settings path. An interrupted operation
blocks ordinary mutation and synchronisation. Retrying the same reconstruction
with the same paths and `--force` is an explicit recovery action. It can repeat
the destructive operation, so users must preserve unique edits and establish
the authoritative copy before retrying.
Completion records the settings update before applying it. A later invocation
can finish a pending settings commit; if the settings file has independently
changed, it must stop for review rather than overwrite those changes. The
pending record is removed only after completion and settings persistence.
## Ownership of persistent state
The selected local database name identifies the dataset. Its runtime state is
further scoped by the canonical physical Vault path. This includes Journal
checkpoints, scanner state, replication-result queues, file-reflection
provenance, and storage-event snapshots. Paths identify local ownership; they
are not sent to the remote as a new document identity.
A local database reset invalidates every Vault scope associated with that
database, because each scope refers to the same discarded revision history.
Other local database suffixes retain their state. PouchDB database names and
Commonlib's reconstruction contract remain unchanged.
KV writes replace the file atomically. Invalid persisted data must fail
initialisation instead of becoming an empty checkpoint history. Closing a KV
handle must not overwrite the file from an old in-memory snapshot.
## Legacy state
The former shared `keyvalue-db.json` and Vault-local `.livesync-snapshot.json`
do not record their complete owner. A single surviving database directory does
not prove which physical Vault produced those records. Automatically importing
them can replay events against the wrong database; dropping them and immediately
scanning can misinterpret file absence before remote history is recovered.
Preserve unowned legacy state. When it contains work, stop ordinary mutating
commands and require explicit reconstruction from a verified source. Inspection
remains available. This deliberately requires more user action than automatic
migration, and must not silently delete the old records.
Check the selected scope's completion flag before reading legacy files. For an
unreviewed scope, inspect both legacy files after settings and scoped KV loading,
but before readiness starts scanning or event replay. Distinguish non-empty
unowned state, invalid JSON or shape, and read failures. Report affected paths
once per invocation on stderr without logging file contents. Read commands may
continue inspection; ordinary writes stop, and explicit reconstruction retains
the files. Once reconstruction has succeeded for that scope, legacy parse or
read failures must not block it again. The active scoped KV still fails
initialisation on corruption.
## Evidence and limits
The original Object Storage scenario uses encryption, path obfuscation, and a
second-generation file revision. Without settings writeback, A reopens an empty
database while sharing the fetched database's Journal history, creates an
independent root, and receives B's same-content resolution tombstone without
the original surviving revision. The original assertion fails because A's
physical file disappears. The explicit `--write-settings` control preserves
the file and the original revision. The remote and B retain the content in this
reproduction; it does not demonstrate irreversible loss.
Validation of the correction must check bytes and revision ancestry after
restart, interrupted reconstruction admission, pending settings completion,
legacy-state preservation, and reset isolation. A successful exit or an empty
queue alone does not establish preservation.
Local validation passed the encrypted Object Storage CLI regression and the
explicit-settings control on Node.js 26. Unit tests cover failed-operation
admission, saved running/completed markers, settings-edit preservation, scoped
stores, reset isolation, corrupt KV files, and legacy-state refusal. The marker
tests inject interruption states; they do not emulate a machine power failure.
The shared device-value JSON file retains its existing concurrency limitations.
This change does not establish concurrent CLI writers to one database directory
as a supported workflow. Native Obsidian and other transport behaviour require
their own runtime evidence.
@@ -1,7 +1,7 @@
---
date: 2026-09-04
commonlib-version: "0.1.21"
self-hosted-livesync-version: "1.0.24"
date: 2026-10-25
commonlib-version: "0.2.0"
self-hosted-livesync-version: "1.0.40"
status: accepted
---
@@ -104,14 +104,22 @@ For new behaviour, keep Obsidian-specific presentation or registration in an Obs
### A small serviceFeature: language initialisation
`src/serviceFeatures/onLayoutReady/enablei18n.ts` declares only `setting`, `API`, and `appLifecycle`:
`src/serviceFeatures/onLayoutReady/enablei18n.ts` declares only `setting`, `API`, and `appLifecycle`, with an explicit dependency type:
```typescript
export const enableI18nFeature = createServiceFeature(async ({ services: { setting, API, appLifecycle } }) => {
// Apply the language, persist a change, and register unload clean-up.
});
type EnableI18nHost = NecessaryServices<"setting" | "API" | "appLifecycle", never>;
export const enableI18nFeature = createServiceFeature(
async (host: EnableI18nHost): Promise<boolean> => {
const { setting, API, appLifecycle } = host.services;
// Apply the language, persist a change, and register unload clean-up.
return true;
}
);
```
The host annotation keeps the dependency contract visible because `createServiceFeature()` does not infer dependency keys from the callback body. This feature retains its `Promise<boolean>` result.
The local `ObsidianLanguageAppliedNotice` class is still appropriate. It owns one replaceable Obsidian `Notice` and has an explicit `clear()` lifetime operation. The class is not used as a service locator, and the serviceFeature owns its construction and host binding.
### Operation and composition: database preparation
@@ -130,6 +138,22 @@ The operation does not need an application Module identity.
### Ordered start-up composition and registration-only features
Compatibility review uses distinct controller and composer dependencies:
```typescript
type CompatibilityReviewDependencies = NecessaryServicesInterfaces<
"setting" | "config" | "vault" | "control",
never
>;
type CompatibilityReviewHost = NecessaryServices<
"setting" | "config" | "vault" | "control" | "appLifecycle" | "API",
never
>;
```
The controller receives the four interface-only Services it uses. The composer adds `appLifecycle` and `API` for ordered lifecycle handlers and command registration; the priority-30 compatibility review remains after flag-file recovery.
Configured Vault admission and the checks which follow database preparation are composed by `src/serviceFeatures/startupLifecycle/`. The directory keeps onboarding admission, compromised-chunk inspection, incomplete-document repair, Config Doctor, and the obsolete bulk-send setting migration as separate operations. One feature composer owns their order and receives the compatibility-review wait operation explicitly; an individual operation does not call the composer.
The layout-ready admission handler uses priority 1. This preserves ordinary priority-0 host integration before admission, while keeping an unconfigured Vault outside the flag-file recovery handlers at priorities 5, 10, and 20, and the compatibility review at priority 30. Admission belongs to one plug-in process: an initially unconfigured process remains inert until setup restarts it, and declining the requested restart does not trigger an in-process reconfiguration. Changing an admitted process back to unconfigured retires its Config Doctor and incomplete-document repair request handlers. The handlers also recheck the current configured state and database readiness when invoked, so a pending restart cannot expose partially initialised or retired state. The first-initialise handler rechecks admission before retaining the established order after the file watcher has been started: database readiness, compromised chunks, incomplete documents, compatibility review, Config Doctor, and the bulk-send setting migration.
@@ -144,7 +168,19 @@ The state remains private to the composed feature. It does not become a `LiveSyn
### Implemented composition: conflict resolution
Conflict checking and resolution are composed for every host by `useConflictResolutionFeature`. The feature owns its `QueueProcessor` privately and registers the conflict Service handlers directly. Its operations receive explicit collaborators for settings, active-file state, database and storage access, replication, logging, and host events. No consumer locates a conflict Module or retains the queue.
Conflict checking and resolution are composed for every host by `useConflictResolutionFeature`. The feature owns its `QueueProcessor` privately and registers the conflict Service handlers directly. Its host type selects the required service and module contracts and adds the borrowed logger explicitly:
```typescript
type ConflictResolutionHost = NecessaryServices<
"appLifecycle" | "conflict" | "database" | "replication" | "setting" | "vault",
"databaseFileAccess" | "fileHandler" | "storageAccess",
InjectableServiceHub
> & {
readonly log: LogFunction;
};
```
The operations receive explicit collaborators for settings, active-file state, database and storage access, replication, a borrowed `LogFunction`, and host events. `LiveSyncBaseCore` supplies `log` using `createInstanceLogFunction("SF:ConflictResolution", this.services.API)`. Because feature composition precedes legacy Module binding, the host supplies the existing lazy service logger and does not read `logResource.value` before binding. `ModuleLog` and the host retain resource ownership. No consumer locates a conflict Module or retains the queue.
The scheduling queue remains one state owner. It publishes `conflictProcessQueueCount`, coalesces pending checks for the same path, and makes `ensureAllProcessed()` wait for conflict resolution to finish. Repeated resolver invocations for one path retain only the newest waiting request and close an active comparison for that path before waiting for the per-file resolver, while comparisons for other paths remain open. Resolution remains host-neutral and communicates dialogue cancellation through `services.context.events`, so CLI, WebApp, and Obsidian compositions use their own selected event channel.
+5 -4
View File
@@ -160,10 +160,11 @@ preflight failure. If a required setup decision cannot be made in a headless
CLI invocation, the command must fail with a clear remediation message rather
than report success after cancellation.
`--write-settings` retains the Rebuilder's selected local database name and
lasting settings changes for subsequent invocations. Without this option, the
existing CLI policy leaves the settings file unchanged, so a later command can
reopen the previously selected database.
The unreleased CLI correction for #1304 always retains the Rebuilder's selected
local database suffix after successful `fetch` or `rebuild`. `--write-settings`
still controls other lasting settings changes. Earlier versions required that
option to retain the database selection; omitting it could reopen the previous
database on the next invocation. The examples retain the option for compatibility.
## State which follows the selected identity