diff --git a/.env.example b/.env.example index 1948fafe..2bb77eee 100644 --- a/.env.example +++ b/.env.example @@ -153,3 +153,41 @@ MCP_DOCMOST_PASSWORD= # Per-request output-token ceiling for the anonymous assistant (default: 512). # Worst-case output per accepted call = agent steps (5) × this value. # SHARE_AI_MAX_OUTPUT_TOKENS=512 + +# --- GIT-SYNC (native two-way Docmost <-> git Markdown sync) --- +# Master switch. Off by default. When 'true', GIT_SYNC_SERVICE_USER_ID below is +# REQUIRED (the service account that git-originated create/move/rename/delete are +# attributed to) — the server refuses to boot with sync enabled and no user id. +# GIT_SYNC_ENABLED=false +# +# Serve the per-space vaults over smart-HTTP (the /git host). Defaults to +# GIT_SYNC_ENABLED when unset. +# GIT_SYNC_HTTP_ENABLED=false +# +# REQUIRED when GIT_SYNC_ENABLED=true: id of the user that git-originated page +# operations (create / move / rename / delete) are attributed to. +# GIT_SYNC_SERVICE_USER_ID= +# +# Where the per-space bare repos / working vaults live. +# Defaults to "/git-sync". +# GIT_SYNC_DATA_DIR= +# +# Optional remote URL template to mirror each space's vault to (e.g. a git host). +# Leave unset to keep vaults local-only. +# GIT_SYNC_REMOTE_TEMPLATE= +# +# Path to the SSH private key used when pushing to GIT_SYNC_REMOTE_TEMPLATE. +# GIT_SYNC_SSH_KEY_PATH= +# +# Poll-safety interval in ms — the cadence of the background reconcile cycle +# (default: 15000). +# GIT_SYNC_POLL_INTERVAL_MS=15000 +# +# Debounce window in ms for collapsing bursts of page edits into one sync cycle +# (default: 2000). +# GIT_SYNC_DEBOUNCE_MS=2000 +# +# Defense-in-depth absolute cap on soft-deletes applied per push cycle +# (default: 5). A non-convergent / phantom-absence cycle can never trash more +# than this many pages without an explicit override. +# GIT_SYNC_MAX_DELETES_PER_CYCLE=5 diff --git a/apps/client/src/features/page-history/components/history-item.tsx b/apps/client/src/features/page-history/components/history-item.tsx index 03a0c0f7..bf31a7ed 100644 --- a/apps/client/src/features/page-history/components/history-item.tsx +++ b/apps/client/src/features/page-history/components/history-item.tsx @@ -108,10 +108,12 @@ function AiAgentBadge({ } /** - * Badge marking a version written by the git-sync VaultGit pull (provenance §8.1). - * Like {@link AiAgentBadge} it is ADDITIVE — shown next to the human author — - * but a git-sync edit is NOT an agent edit, so it is rendered as a small, neutral - * badge with NO deep-link (there is no AI chat behind it). + * Badge marking a version produced by git-sync (provenance §8.1). The history + * version is created on the PUSH path — when an incoming git body is written + * back into the Docmost doc — not by the pull itself. Like {@link AiAgentBadge} + * it is ADDITIVE — shown next to the human author — but a git-sync edit is NOT an + * agent edit, so it is rendered as a small, neutral badge with NO deep-link + * (there is no AI chat behind it). */ function GitSyncBadge({ authorName }: { authorName?: string }) { const { t } = useTranslation(); diff --git a/apps/client/src/features/space/components/edit-space-form.tsx b/apps/client/src/features/space/components/edit-space-form.tsx index 8bf176d9..95077e3d 100644 --- a/apps/client/src/features/space/components/edit-space-form.tsx +++ b/apps/client/src/features/space/components/edit-space-form.tsx @@ -52,6 +52,9 @@ export function EditSpaceForm({ space, readOnly }: EditSpaceFormProps) { }); } catch (err) { setGitSyncEnabled(previous); // revert on failure + // The mutation surfaces a toast via onError; still log the raw error so it + // is not silently swallowed (AGENTS.md). + console.error("Failed to toggle git-sync for space", err); } }; diff --git a/apps/server/src/collaboration/extensions/persistence.extension.ts b/apps/server/src/collaboration/extensions/persistence.extension.ts index adb77722..ed43cd40 100644 --- a/apps/server/src/collaboration/extensions/persistence.extension.ts +++ b/apps/server/src/collaboration/extensions/persistence.extension.ts @@ -182,7 +182,9 @@ export class PersistenceExtension implements Extension { // agent event in the same window). §15 H2. // Provenance precedence: agent > git-sync > user (see resolveSource). A // 'git-sync' store is NOT given an immediate history snapshot — it is - // debounced like a human edit (git-sync writes are full-body replaces). + // debounced like a human edit (a git-sync write is a block-level merge into + // the live doc, so it reads like an incremental human edit, not a bulk + // import that would warrant its own immediate snapshot). const lastUpdatedSource = resolveSource( this.consumeAgentTouched(documentName), context?.actor, diff --git a/apps/server/src/integrations/git-sync/services/yjs-body-merge.ts b/apps/server/src/integrations/git-sync/services/yjs-body-merge.ts index 40e73d4c..c1dab666 100644 --- a/apps/server/src/integrations/git-sync/services/yjs-body-merge.ts +++ b/apps/server/src/integrations/git-sync/services/yjs-body-merge.ts @@ -5,8 +5,8 @@ import { diff3Plan } from './three-way-merge'; /** * Block-level merge of an incoming (git) page body into a LIVE Yjs document, * replacing the previous full-body "delete everything + re-insert" write that - * clobbered concurrent human edits on every sync (review #5 — "запись делать - * через мерж"). + * clobbered concurrent human edits on every sync (review #5 — "do the write as a + * merge"). * * Strategy: diff the two documents at TOP-LEVEL BLOCK granularity (an LCS over a * canonical structural serialization of each block) and apply only the minimal