[report][#119] Повторный автотест git-sync (после фиксов) — дублирование мёртво, open-editor converged; вскрыта клиентская collab-race (HIGH) + same-para conflict git-wins #223

Closed
opened 2026-06-26 20:34:04 +03:00 by Ghost · 0 comments

Повторное автономное тестирование PR #119 feat/git-sync через web-test-orchestrator (30 агентов, та же оркестрация: recon → 6 слайсов вкл. git-roundtrip и dual-edit → независимый verifier → синтез). Прогон против СВЕЖЕГО feat/git-sync (c1c87c21) — со всеми моими фиксами (data-loss дедуп по schema-default, open-editor convergence, ревью-1863, Details.open).

🎯 Вердикт по ранее найденным data-loss багам

  • Runaway-дублирование (движок, schema-default ключ) — МЁРТВО. Никакого безграничного роста; vault стабилен. Интермиттентный one-time дубль, который всё же всплыл (см. ниже), трассируется к КЛИЕНТСКОЙ гонке collab-connect, а не к движку реконсиляции.
  • ⚠️ Open-editor convergence — фикс работает (git теперь доезжает до открытого редактора, не откатывается молча). НО это вскрыло обратную сторону: при конфликте в ОДИН И ТОТ ЖЕ абзац git теперь молча побеждает живую правку человека, игнорируя тумблер autoMergeConflicts=OFF (medium, см. ниже). Раньше терялся git, теперь — правка человека в конфликтном блоке.
  • 🔴 Доминирующий НОВЫЙ баг — клиентская гонка collab-connect (не git-sync): печать в только что созданную страницу ДО подключения collab → тихая потеря тела (HIGH), съеденные первые символы, ДВОЙНОЕ сохранение в collab-доке. Именно это загрязняет git (пустой первый коммит, интермиттентный дубль в .md). Корень — редактор/collab, не движок git-sync.

git-sync findings (6)

[medium] Same-paragraph concurrent edit: git silently overwrites the human's in-flight UI edit with zero conflict indication (silent data loss) (conf: verified, by dual-edit)

Repro: 1) Open a page in the UI editor. 2) Edit PARA2 live in the editor (now in Yjs, not yet committed to git). 3) Within ~1s, from a fresh clone push a different PARA2 value (push fast-forwards because the human edit hasn't reached git yet). 4) Watch the open editor with NO reload.

Evidence: Reproduced. Live editor before merge 'PARA1 BASE-A | PARA2 IVHUMAN-LIVE | PARA3 BASE-C'; the race push WON fast-forward at attempt=1 with base_was=[PARA2 BASE-CLEAN] (proving the human edit was still in-flight, absent from git). ~10s after the push, with NO reload, the live editor flipped to 'PARA2 IVGIT-WON-7Z9' — the human's keystrokes vanished. Oracles: conflict markers in DOM=False (no <<<<<<< / >>>>>>>), toast/alert count=0, zero console errors. Hard reload and a fresh git clone both show the git text — durable silent loss. Source: three-way-merge.ts lines 6-8 + diff3Plan fallback line 219 document 'git wins' as intended, but it is silent UI data loss with no conflict surfaced. Screenshot iv_B3win.png. (The opposite ordering — human edit reaches git first — yields a clean, correct git non-fast-forward push rejection, so the side that loses depends purely on who reaches main first.)

[medium] Concurrent git push during a server-side sync tick returns HTTP 503 / 'remote end hung up unexpectedly' and is not retried by git (conf: verified, by git-roundtrip-tester / dual-edit)

Repro: git clone the vault, commit a change, then push while git-sync (or its background poll) holds the per-space lock. The push fails; an immediate retry usually succeeds.

Evidence: Reproduced by two testers and the verifier. Server returns a deliberate application-level 503 on POST /git/.git/git-receive-pack with 'Content-Type: text/plain' and 'Retry-After: 1' and body 'git-sync busy, retry'; git prints 'error: RPC failed; HTTP 503 ... send-pack: unexpected disconnect while reading sideband packet / fatal: the remote end hung up unexpectedly' (exit 1). git's smart-HTTP push does NOT honor Retry-After, so it surfaces a hard failure. Rates: 11/25 (git-roundtrip) and 7/10 (dual-edit, with Redis lock confirmed held). A 503'd push does NOT land; the retry then needs a git pull --rebase because the server has moved on. Source: git-http.service.ts:270-289 (GitSyncLockHeldError -> 503 + Retry-After), git-sync.orchestrator.ts ingestExternalPush:204-250 (try-lock, throws on skip), space-lock.service.ts (Redis SET NX non-blocking lock). Broader than originally reported: the background ~15s poll, not just a human UI edit, holds the lock. Rate here is amplified by this stand's aggressive ~10-15s sync cadence.

[low] Wide outbound lag (~10-15s) to git, and the first post-create commit captures an empty body (frontmatter only) before the body lands (conf: verified, by dual-edit)

Repro: Create a new note, type a title + paragraphs, then git pull the vault repeatedly and watch the .md size/content over time.

Evidence: Reproduced (lag if anything worse than claimed). Exporter is alive/batched ('docmost: sync N page(s)' every ~15-45s). Every UI-created note first enters git as 59 bytes = frontmatter only (---\ngitmost_id:<uuid>\n---, no body); decisive diff 30b14a3 shows the note entering purely as a rename with 0 insertions. Body IS safely persisted server-side (page reload rendered the full body), so no DB data loss — the gap is purely in the outbound git export. This is exactly the window the silent-overwrite conflict lives in: a git user pulling right after a UI edit sees a stale/empty body and may base a push on it. Caveat: in the verifier's window the body never propagated to git within 15+ min on a contended/wedged stand, so the 'eventually consistent ~10-15s' optimistic part was not always observable.

[low] Asymmetric conflict handling: human-edit-first yields a safe explicit push rejection (correct git), git-first risks loss (conf: verified, by dual-edit)

Repro: Let the human's UI edit reach git FIRST (wait ~10-15s after the UI edit), THEN push a stale-based same-file change from a clone.

Evidence: Reproduced the safe half: stale push is rejected with '! [rejected] ... (fetch first)' / 'Updates were rejected because the remote contains work you do not have locally', git exit 1 — the pusher is clearly told and nothing is lost. Verifier note: this is plain, correct git non-fast-forward ref-locking (the report concedes this), and is ref-level not conflict-aware — with this vault's constant churn ANY stale push to main is rejected regardless of file. The dangerous contrast (git-first -> clean silent UI overwrite) was only half-substantiated by the verifier: in their run the git push was accepted but the editor kept the human text, producing a stuck DB/git divergence (autoMergeConflicts=false skip-on-conflict) rather than a clean silent overwrite. The silent-overwrite data-loss path itself is separately verified under the same-paragraph in-flight-race finding above.

[medium] Intermittent page-body duplication during a git-sync cycle (user typed 1 copy, ends with 2) (conf: likely, by git-roundtrip-tester)

Repro: In the UI create a rich page (heading+callout+code+nested list), let git-sync push, then reload. Observed on 'GRT Beta 90706'. NOT reliably reproducible on demand — a rare intermittent collab merge race; GRT Alpha authored the same way did not duplicate.

Evidence: Real, durable git artifact: history of 'GRT Beta 90706.md' = dfe4a45 headings=0 -> dd046cd headings=1 -> 6231be4 headings=2; git diff dd046cd 6231be4 = '1 file changed, 15 insertions(+)' inserting a verbatim duplicate block (# Heading, intro, > [!info] callout, ```const v=42, nested list). Both commits authored by server 'Docmost Sync' with NO UI edit between, so the export itself duplicated the body. Verifier could not reproduce a FRESH duplication (controlled retries stayed at 1 copy via the page API) — hence 'likely', not 'verified' — but the git history is permanent evidence and the same duplication is independently verified at the UI/collab layer (see 'Editor content duplicated' in the UI findings). Same collab/Yjs merge-on-reconnect race class, amplified by the sync export.

[low] Git smart-HTTP host + clone working with auth enforced (verified working, not a defect) (conf: verified, by recon)

Repro: curl info/refs with and without basic-auth; then git clone http://admin%40test.local:gitmostdev2026@localhost:3000/git/.git

Evidence: GET /git/.git/info/refs?service=git-upload-pack -> 200 WITH basic-auth, 401 WITHOUT (correct enforcement); body is a valid git smart-HTTP pkt-line stream. git clone exited 0 and produced 209 .md files (one per page; the vault grew during the session as testers added pages). Each file has '---\ngitmost_id:\n---' frontmatter then real markdown (headings, bold/italic, bullet/numbered lists, '- [ ]' tasks, blockquotes, '> [!info]' callouts, fenced ```js code). git log shows 'docmost: sync N page(s)' commits. Healthy baseline for the git-sync feature.

gitmost-ui findings (8)

[high] Silent body-content data loss when typing into a freshly-created page before collab sync (conf: verified, by editor-content-tester)

Repro: In space General click '+' (Create page). As soon as the blank editor appears (type within ~2-3s, before the Hocuspocus/Yjs provider fires 'synced'), click into the body and type text. Wait a few seconds, reload (or open in a fresh browser). Body is empty again ('Write anything. Enter / for commands'); typed text is gone, no error. Workaround: wait ~10s before typing, OR reload once before typing.

Evidence: Independently reproduced, deterministic across 2 verifier runs. Fresh page + 3s wait: immediate editor DOM showed typed text (imm=2) but after reload DOM=0 AND server-stored content via POST /api/pages/info = 0 nodes, with 4xx/5xx=[] and console-errors=0 (fully silent). Threshold sweep: 3s=LOST, 6s=partial, 10s=OK; settle-reload-before-typing=OK. Control: editing an existing/synced page persists perfectly (imm->reloadDOM->API all =1), scoping the bug to fresh pages only. Mechanism: editor is editable before the provider fires 'synced', so the server's initial empty doc clobbers the early local edits. Screenshots: scratchpad/ec/loss_0_typed.png, loss_0_reload.png; scripts iv_repro.py, iv_login.py.

[medium] Initial keystrokes (and leading title characters) dropped when typing into a just-created page before the collaborative editor connects (conf: verified, by git-roundtrip-tester)

Repro: Click 'Create page' and immediately type a title + heading/paragraph into the body before the collab WebSocket connects. Leading keystrokes are silently discarded while the editor still shows the inviting 'Write anything' placeholder.

Evidence: Decisive A/B (vr_ab.py): 'body contenteditable right after create: false (+8.29s)'. PHASE-A text typed while read-only -> body stayed empty, keystrokes dropped. Body became editable at +10.88s (coincides with ws collab connect); PHASE-B text typed after editable was captured. After reload only the AFTERREADY text persisted. Reproduced across ~10 runs; connect time varied +2s..+21s. Corroborating: sidebar littered with 'HEADINGFAST'/'INGFAST'/'NGFAST'/'ADINGFAST' (same title with 0/2/3/4 leading chars eaten) and the pre-existing truncated titles 'ent Test'/'ntent Test'/'ntent Te' (from 'Content Test') plus clone filenames matching — the same race also eats leading title keystrokes during the button->title focus transition. Note: /api/pages/info returns content:null for collab-stored bodies, so reload-DOM is the reliable oracle. Closely related to the high-severity fresh-page body-loss finding (same collab-connect race).

[medium] Editor content duplicated (stored twice) in the persisted collab doc after reload on a fresh page (conf: verified, by editor-content-tester)

Repro: Type a multi-block rich document (H1, H2, bold/italic paragraph, bullet list w/ nested item, numbered list, task list) into a fresh page, then reload. Intermittent (a clean create-close-reopen-type flow did NOT always duplicate) — a race in the same collab-sync neighborhood as the loss bug.

Evidence: Verifier reproduced from scratch. Backend storage of the originally-reported page (pageId ZQ4LgGQVRt) still doubled: POST /api/pages/info -> content contains the entire block sequence TWICE with DISTINCT node ids (two 'Heading One' obqbpkxwukbo + ippnwcwkspcr, two 'Heading Two', doubled bullet/ordered/task lists) — distinct ids prove two real stored copies, not a DOM artifact. Live repro: freshly typed multi-block doc rendered twice after reload (iv_dup/d2_B1_ctx.png) and survived a fully fresh browser context (d2_B1_fresh0.png), so it lives in the server-side Yjs/Hocuspocus doc shared by all clients. In the verifier run leading blocks were LOST while the tail was DUPLICATED — same reload-time collab race as the loss bug.

[medium] "New chat" is a no-op after a failed first turn — stale failed message and red error banner persist (conf: verified, by ai-chat-ui)

Repro: Open AI chat (fresh, no chat selected). Send a message; with no provider it fails with the red 'AI provider not configured' banner and the user bubble stays. Click header 'New chat'. Expected: clean empty chat. Actual: old user message AND red banner remain; nothing resets. Close+reopen the window DOES clear it.

Evidence: Reproduced on the live stand (aichat_refine.py): after Send +9s user msg present + banner in DOM; after New chat x1 and x2 both still present (persistent no-op); after Close+Reopen both gone. Screenshot R2_after_newchat_settled.png. Root cause confirmed in source: ChatThread key={threadKey} (ai-chat-window.tsx:624); reconciler use-chat-session.ts:216 if (activeChatId !== thread.chatId) only remounts on activeChatId change; after a failed first turn no id was adopted so both are null, startNewChat (ai-chat-window.tsx:216) sets activeChatId=null (no-op), threadKey never changes, useChat keeps the optimistic message + error. Comment at ai-chat-window.tsx:210-215 acknowledges the no-op path but only disarms the adoption fallback — it never resets the visible thread. (Surfaced via the env's unconfigured-provider 503, but the reset defect is real and provider-independent.)

[low] Trash page renders a blank table with no loading indicator for ~2-5s on fresh reload / direct URL load (conf: verified, by tree-nav)

Repro: Open /s/general/trash directly or hard-reload (F5). Intermittently the route shows only the 'Trash' heading + 30-day banner (or nothing at all early) with NO table/rows and NO spinner/skeleton, then rows pop in. In-app navigation (Space menu -> Trash) renders instantly.

Evidence: 15x direct-reload loop: rows first appear 2-5s late, and on 4/15 reloads the table stayed empty the entire 6s sample window. anyLoader=False on ALL 15 reloads (no class=loader|skeleton|spinner|loading, no [role=progressbar]/[aria-busy]). At t=1s the whole route is unmounted (main.innerText=''), then heading+banner+rows appear together. In-app pushState nav firstRowAt=0.05s. Data integrity fine: /api/pages/trash returns HTTP 200 every time. Root cause: slow first-paint with zero loading state, heavily amplified by Vite dev-mode cold module compilation (production build would be far faster). Low severity; a user reloading mid-load could momentarily misread it as 'trash is empty'. Screenshots stuck_empty.png, probe0.png.

[low] Ambient jotai 'atomFamily is deprecated' console warning on every authenticated page (conf: verified, by recon)

Repro: Load any authenticated page and read the browser console.

Evidence: Console WARNING (not error): '[DEPRECATED] atomFamily is deprecated and will be removed in v3. Please use the jotai-family package instead'. Appears on initial /home load and again after full reload (reload match count 1), so it is ambient page-load noise, not action-triggered. No crash, no blank page. Benign library deprecation noise — flagged so it is not mistaken for an action-triggered failure during charters.

[low] Git-sync space-settings UI exposes only two toggles (no remote URL / branch / sync-now / status) — map fact, not a defect (conf: verified, by recon)

Repro: Space (/s/general) -> Space menu -> Space settings -> Settings tab; scroll the git-sync section.

Evidence: The git-sync section contains exactly two controls: switch 'Enable Git sync' (ON) and switch 'Auto-merge conflicts on push' (OFF), with explanatory copy. DOM dump of the modal returned only these two switches; the only text inputs are Name/Slug/Description and the Members search box. NO 'Sync now' trigger, NO remote-URL field, NO branch field, NO last-sync timestamp/status. Screenshot 70_settings_tab.png. Consequence for QA: sync state must be observed via the git smart-HTTP host / a fresh clone, not the settings panel.

[low] Admin 'Analytics / tracker' setting injects verbatim markup into <head> of public share pages (intentional, admin-gated stored-script surface) (conf: verified, by share-search-settings)

Repro: /settings/workspace -> 'Analytics / tracker' section -> set a snippet -> load a public /share/:shareId/p/:pageSlug page.

Evidence: Verifier set a probe '<script>/*...$& `'...*/window.__probe=1;</script>' via admin POST /api/workspace/update (200, persisted verbatim incl. regex-special $&). Anonymous GET of the public share SEO route returned HTTP 200 with the snippet injected byte-for-byte before </head> (inject-tracker-head.util.ts uses a function replacer, deliberately no escaping; single injection site share-seo.controller.ts on the public route only). Write is admin-gated server-side (CASL Manage Settings -> ForbiddenException for a MEMBER). Resetting trackerHead='' removed it. Working as designed (GA/Yandex.Metrika equivalent), NOT a vulnerability — flagged as a stored-script design surface worth awareness, confirmed restricted to trusted admins.

False positives (убиты verifier-проходом) (5)

  • Repeated React 'flushSync was called from inside a lifecycle method' warnings during block insertion (reported by editor-content-tester). Verifier verdict: SPECULATIVE/not-a-bug. The exact warning reproduced only intermittently (counts 2,8,0,0 across 4 runs) and was NOT tied to insertion — in the decisive run with console-ordered markers it fired only at editor MOUNT (right after 'ws connected', before any slash-menu use); the subsequent insertions added none. It is a known benign React DEV-ONLY warning from @tiptap/react EditorContent (PureEditorContent), stripped from production builds. Blocks render and persist; no crash. The 'repeatedly while inserting blocks' causation does not hold.
  • Code block drops the first few typed characters at creation, e.g. 'const x = 42;' -> 'x = 42;' (reported by editor-content-tester). Verifier verdict: NOT A BUG. The created element is
    x = 42;
    — 'const' was consumed as the fenced-code-block LANGUAGE info-string (the standard js/python Markdown shortcut via tiptap input rule /^([a-z]+)?[\s\n]$/), not dropped. Disconfirming controls: +Enter then type, and ```+space-first then type, both preserve 'const x = 42;' fully; deterministic even at slow 80ms/char typing, so NOT the speculated typing-speed input-capture race. Expected behavior.
  • Authenticated global search shows no empty-state (blank panel) for zero results, unlike public search (reported by share-search-settings). Verifier verdict: COULD NOT REPRODUCE. After the query resolves the authenticated spotlight renders 'No results found...' exactly like the public share search (auth_empty.png; aria-live status 'No results found'). Source search-spotlight.tsx:104-106 renders <Spotlight.Empty> when query.length>0 && !isLoading && results===0. The only difference vs the share variant is an extra !isLoading guard, so the auth panel is momentarily blank only during the sub-second in-flight loading window — the original tester almost certainly screenshotted that transient loading frame. Steady state is correct and consistent; at most a cosmetic sub-second flicker, not a missing empty-state.
  • Debounced-save race: expanded tree state occasionally reverts to collapsed after a move-then-immediate-reload (reported by tree-nav, originally self-tagged speculative). Verifier verdict: SPECULATIVE/inconclusive — could not establish a reliable repro; in dedicated tests manual expand+reload and manual collapse+reload both persisted correctly. Observed once and confounded by auto-expand-on-drop (not a user toggle). Not a confirmed defect.
  • Different-paragraph concurrent edits merge cleanly and both survive live without reload — 'verified working' (reported by dual-edit). This is a GOOD-NEWS claim, not a bug; flagged here only because the verifier COULD NOT REPRODUCE it end-to-end: the outbound DB->git sync was wedged on the contended shared stand (HEAD frozen at an external 'gitfirst' commit for 9+ min, bodies stuck in DB, never exported), so the clean-merge roundtrip was neither confirmable nor refutable during the verifier's window. The merge logic is NOT declared broken — just unconfirmed under a stalled-sync condition (plausibly transient contention from parallel agents).

Подробный мета-отчёт

gitmost git-sync — QA Synthesis Meta-Report

Stand: gitmost (Docmost fork) — UI http://localhost:5173, API + git smart-HTTP http://localhost:3000, git-sync space "General" (spaceId 019f01fd-3350-7cc9-9446-e20fb81e6c1d). AI provider intentionally NOT configured.
Methodology: web-test-orchestrator skill — recon → mapping → charters → execution → FEW HICCUPPS oracle evaluation → independent Doer/Verifier pass → this synthesis. Hard rules: evidence-before-claim, disconfirm-by-default, reload-to-test-persistence.

Headline numbers

  • Raw findings submitted: 24 (across 7 agents).
  • Confirmed REAL after independent verification: 14 distinct items (8 gitmost-ui + 6 git-sync), after dedup/merging.
  • False positives / not-a-bug / unconfirmed: 5 (see list).
  • Env-limitations (correctly handled, not bugs): AI-chat send 503 + AI settings page render — both degrade gracefully (friendly banner, no crash/blank).
  • Top risk: 1 HIGH (fresh-page silent body-content loss) + 1 silent git-wins data-loss (medium) — both are SILENT data loss with no user-facing signal, the most dangerous class.

Agents that ran and how each stage went

Stage 1 — Recon (1 agent)

Established a clean baseline: all main authenticated screens load with zero console errors / zero HTTP >=400 / no blank pages; ProseMirror mounts and survives reload. Mapped the git-sync settings surface (only two toggles), confirmed the git smart-HTTP host + clone works with auth enforced, and pre-flagged two pieces of ambient noise (jotai atomFamily deprecation warning; AI 503 env limit) so later charters wouldn't misread them. Recon was high-value: it produced the map facts that made later git-sync charters observe sync state via clone, not the (insufficient) settings panel. The only verifier hiccup was self-inflicted rate-limiting (429 on repeated logins) — a tooling artifact, not a product bug.

Stage 2 — Six slice testers

Agent Coverage Real found FP/not-a-bug
editor-content-tester Fresh-page typing, rich blocks, slash menu, persistence Silent body loss (HIGH), content duplication (MED) flushSync-on-insert (benign DEV warning), code-block 'first chars dropped' (it's the markdown language info-string)
tree-nav Page tree, drag-move, trash, expand-state persistence Trash blank-table slow first-paint (LOW) Expand-state-reverts race (inconclusive)
share-search-settings Public share, search empty-state, workspace/AI settings Analytics/tracker head-injection design surface (LOW) Auth search 'no empty-state' (transient loading frame, steady-state correct)
ai-chat-ui AI chat panel, send, New-chat reset 'New chat' no-op after failed first turn (MED) — (the 503 itself is the env limit)
git-roundtrip-tester UI-author → git export → clone roundtrip git push 503 during sync (MED), early-keystroke/title drop (MED), intermittent body duplication (MED)
dual-edit Concurrent UI-edit vs git-push conflict matrix Same-para git-wins silent overwrite (MED), push-bounce 503 (MED), outbound lag/empty-first-commit (LOW), asymmetric rejection (LOW) 'Different-para clean merge works' (good-news claim, unconfirmable while sync wedged)

Stage 3 — Independent verifiers (Doer/Verifier pass)

The verifier pass did exactly its job and is the most valuable stage in this run: it upgraded strong findings with decisive root-cause evidence (source line refs for the New-chat no-op and the git-wins merge; GIT_CURL_VERBOSE wire capture proving the server-issued 503+Retry-After), and it killed 5 weak findings that would otherwise have shipped as bugs — notably the "code block drops chars" (actually correct Markdown behavior) and the "auth search has no empty-state" (a transient loading frame). Two verdicts came back with placeholder evidence ("e") for the title-truncation and tree-expand findings; I treated title-truncation as confirmed because it is the same robustly-verified collab-connect race as the keystroke-drop finding, and demoted the tree-expand one to false-positive per its speculative twin verdict.

Dedup / merge decisions

  • Fresh-page early-typing loss family: kept the editor-content-tester body-loss as the HIGH headline, merged the git-roundtrip "keystroke drop before connect" + title-truncation into one MEDIUM UI finding (same collab-connect race, distinct mechanism: editor held contenteditable=false / unsynced empty doc clobbers early edits).
  • Concurrent-push 503: git-roundtrip-tester and dual-edit reported the same phenomenon (per-space Redis lock → application-level 503 + Retry-After) — merged into one git-sync finding; verifier confirmed it's broader than reported (the background poll, not just human edits, holds the lock).
  • Body duplication: the UI-layer dup (editor-content-tester, VERIFIED) and the git-sync-cycle dup (git-roundtrip, speculative but with a permanent git-diff artifact) are the same root-cause class; kept as one UI finding + one git-sync finding (the latter marked likely).

Coverage gaps (honest)

  1. AI features are almost entirely untested because no provider is configured. We verified graceful degradation (503 → friendly banner, no crash) and one real reset bug, but token/context badge, streaming, tool-calls, agent roles, MCP, embeddings, and voice-STT were never exercised. This is an ENV limitation, not a product verdict — treat AI as UNTESTED, not "passing".
  2. Sync stalled mid-run. A verifier observed outbound DB→git sync wedged for 9+ minutes after an external push on this contended shared stand, which blocked confirmation of the good-news "different-paragraph clean merge" path and the optimistic "body lands ~10-15s later" claim. The wedge itself (an external push appears to stall the sync loop) is a plausible defect we could NOT pin down — worth a dedicated, isolated repro.
  3. Multi-agent contention polluted timing. Several testers note the stand was being hammered by parallel agents (pages appearing live, vault growing 157→209 files), inflating the 503 rate (44% / 70%) and making some races look more frequent than a normal single-user deployment would see. Severities account for this.
  4. Vite dev-mode artifacts. The trash blank-table and some first-paint slowness are amplified by dev-mode cold module compilation; a production-build pass is needed to confirm real-world severity.
  5. No security depth on the tracker-injection surface beyond confirming admin-gating and verbatim injection — we did not attempt XSS/CSP bypass scenarios on public share pages.

Concrete improvement suggestions for this QA process / skill

  1. Add a collab-readiness oracle. The two worst bugs (silent body loss, keystroke drop) both stem from the editor being interactable before the Yjs/Hocuspocus provider fires synced/connects. A reusable probe should: create a page, assert contenteditable + provider-synced state, and only then type — and as a CHARTER, deliberately type during the unready window to catch the loss. Make "type-before-ready then reload" a standing regression check.
  2. Don't trust /api/pages/info as the persistence oracle for collab bodies — it returns content:null for ydoc-stored bodies. Codify "reload-DOM is the source of truth for collab content; API snapshot is debounced and lags" in the skill's oracle notes, so testers stop mis-scoring loss/dup.
  3. Distinguish "transient loading frame" from "missing state". Two FPs (auth search empty-state, trash blank table) came from screenshotting an in-flight frame. Add a rule: for any "missing UI element" claim, sample the DOM repeatedly until network-idle / the relevant XHR resolves before asserting absence.
  4. Encode Markdown/editor semantics to pre-empt not-a-bug reports. The "code block drops chars" FP was standard fenced-code language info-string behavior. A short "known editor behaviors" crib (language fences, input-rule transforms, smart-paste) would have caught it before it reached a verifier.
  5. Isolate git-sync timing tests from concurrent agents. Race-rate numbers (503 frequency, lag) are meaningless on a contended stand. Provide a single-tenant window or a dedicated space per agent so severities reflect a normal deployment, and instrument the Redis lock + sync cadence directly rather than inferring from push outcomes.
  6. Add an explicit "silent data loss" oracle tier. Three of the most serious findings (fresh-page loss, git-wins overwrite, body dup) produced ZERO console/HTTP signals — the cheap signals missed them entirely; only reload-persistence + backend-storage diffing caught them. The skill should mandate a persistence/round-trip diff for any create/edit/merge charter, not just signal-watching.
  7. Tag env-limitation vs untested explicitly in the final report so an owner doesn't read "AI handled gracefully" as "AI works". We did this; make it a required field.
  8. Verifier discipline paid off — keep it, but require non-empty evidence. Two verdicts shipped with placeholder "e" evidence. Enforce a minimum-evidence gate (artifact path or captured output) before a verdict is accepted, otherwise demote to speculative.

🤖 web-test-orchestrator (multi-agent QA), повторный прогон по PR #119 (после фиксов)

Повторное автономное тестирование **PR #119 `feat/git-sync`** через web-test-orchestrator (30 агентов, та же оркестрация: recon → 6 слайсов вкл. git-roundtrip и dual-edit → независимый verifier → синтез). Прогон против СВЕЖЕГО feat/git-sync (`c1c87c21`) — со всеми моими фиксами (data-loss дедуп по schema-default, open-editor convergence, ревью-1863, Details.open). ## 🎯 Вердикт по ранее найденным data-loss багам - ✅ **Runaway-дублирование (движок, schema-default ключ) — МЁРТВО.** Никакого безграничного роста; vault стабилен. Интермиттентный one-time дубль, который всё же всплыл (см. ниже), трассируется к КЛИЕНТСКОЙ гонке collab-connect, а не к движку реконсиляции. - ⚠️ **Open-editor convergence — фикс работает** (git теперь доезжает до открытого редактора, не откатывается молча). НО это вскрыло обратную сторону: при конфликте в ОДИН И ТОТ ЖЕ абзац git теперь молча побеждает живую правку человека, игнорируя тумблер `autoMergeConflicts=OFF` (medium, см. ниже). Раньше терялся git, теперь — правка человека в конфликтном блоке. - 🔴 **Доминирующий НОВЫЙ баг — клиентская гонка collab-connect** (не git-sync): печать в только что созданную страницу ДО подключения collab → тихая потеря тела (HIGH), съеденные первые символы, ДВОЙНОЕ сохранение в collab-доке. Именно это загрязняет git (пустой первый коммит, интермиттентный дубль в .md). Корень — редактор/collab, не движок git-sync. ## git-sync findings (6) ### [medium] Same-paragraph concurrent edit: git silently overwrites the human's in-flight UI edit with zero conflict indication (silent data loss) _(conf: verified, by dual-edit)_ **Repro:** 1) Open a page in the UI editor. 2) Edit PARA2 live in the editor (now in Yjs, not yet committed to git). 3) Within ~1s, from a fresh clone push a different PARA2 value (push fast-forwards because the human edit hasn't reached git yet). 4) Watch the open editor with NO reload. **Evidence:** Reproduced. Live editor before merge 'PARA1 BASE-A | PARA2 IVHUMAN-LIVE | PARA3 BASE-C'; the race push WON fast-forward at attempt=1 with base_was=[PARA2 BASE-CLEAN] (proving the human edit was still in-flight, absent from git). ~10s after the push, with NO reload, the live editor flipped to 'PARA2 IVGIT-WON-7Z9' — the human's keystrokes vanished. Oracles: conflict markers in DOM=False (no <<<<<<< / >>>>>>>), toast/alert count=0, zero console errors. Hard reload and a fresh git clone both show the git text — durable silent loss. Source: three-way-merge.ts lines 6-8 + diff3Plan fallback line 219 document 'git wins' as intended, but it is silent UI data loss with no conflict surfaced. Screenshot iv_B3win.png. (The opposite ordering — human edit reaches git first — yields a clean, correct git non-fast-forward push rejection, so the side that loses depends purely on who reaches main first.) ### [medium] Concurrent git push during a server-side sync tick returns HTTP 503 / 'remote end hung up unexpectedly' and is not retried by git _(conf: verified, by git-roundtrip-tester / dual-edit)_ **Repro:** git clone the vault, commit a change, then push while git-sync (or its background poll) holds the per-space lock. The push fails; an immediate retry usually succeeds. **Evidence:** Reproduced by two testers and the verifier. Server returns a deliberate application-level 503 on POST /git/<id>.git/git-receive-pack with 'Content-Type: text/plain' and 'Retry-After: 1' and body 'git-sync busy, retry'; git prints 'error: RPC failed; HTTP 503 ... send-pack: unexpected disconnect while reading sideband packet / fatal: the remote end hung up unexpectedly' (exit 1). git's smart-HTTP push does NOT honor Retry-After, so it surfaces a hard failure. Rates: 11/25 (git-roundtrip) and 7/10 (dual-edit, with Redis lock confirmed held). A 503'd push does NOT land; the retry then needs a `git pull --rebase` because the server has moved on. Source: git-http.service.ts:270-289 (GitSyncLockHeldError -> 503 + Retry-After), git-sync.orchestrator.ts ingestExternalPush:204-250 (try-lock, throws on skip), space-lock.service.ts (Redis SET NX non-blocking lock). Broader than originally reported: the background ~15s poll, not just a human UI edit, holds the lock. Rate here is amplified by this stand's aggressive ~10-15s sync cadence. ### [low] Wide outbound lag (~10-15s) to git, and the first post-create commit captures an empty body (frontmatter only) before the body lands _(conf: verified, by dual-edit)_ **Repro:** Create a new note, type a title + paragraphs, then git pull the vault repeatedly and watch the .md size/content over time. **Evidence:** Reproduced (lag if anything worse than claimed). Exporter is alive/batched ('docmost: sync N page(s)' every ~15-45s). Every UI-created note first enters git as 59 bytes = frontmatter only (`---\ngitmost_id:<uuid>\n---`, no body); decisive diff 30b14a3 shows the note entering purely as a rename with 0 insertions. Body IS safely persisted server-side (page reload rendered the full body), so no DB data loss — the gap is purely in the outbound git export. This is exactly the window the silent-overwrite conflict lives in: a git user pulling right after a UI edit sees a stale/empty body and may base a push on it. Caveat: in the verifier's window the body never propagated to git within 15+ min on a contended/wedged stand, so the 'eventually consistent ~10-15s' optimistic part was not always observable. ### [low] Asymmetric conflict handling: human-edit-first yields a safe explicit push rejection (correct git), git-first risks loss _(conf: verified, by dual-edit)_ **Repro:** Let the human's UI edit reach git FIRST (wait ~10-15s after the UI edit), THEN push a stale-based same-file change from a clone. **Evidence:** Reproduced the safe half: stale push is rejected with '! [rejected] ... (fetch first)' / 'Updates were rejected because the remote contains work you do not have locally', git exit 1 — the pusher is clearly told and nothing is lost. Verifier note: this is plain, correct git non-fast-forward ref-locking (the report concedes this), and is ref-level not conflict-aware — with this vault's constant churn ANY stale push to main is rejected regardless of file. The dangerous contrast (git-first -> clean silent UI overwrite) was only half-substantiated by the verifier: in their run the git push was accepted but the editor kept the human text, producing a stuck DB/git divergence (autoMergeConflicts=false skip-on-conflict) rather than a clean silent overwrite. The silent-overwrite data-loss path itself is separately verified under the same-paragraph in-flight-race finding above. ### [medium] Intermittent page-body duplication during a git-sync cycle (user typed 1 copy, ends with 2) _(conf: likely, by git-roundtrip-tester)_ **Repro:** In the UI create a rich page (heading+callout+code+nested list), let git-sync push, then reload. Observed on 'GRT Beta 90706'. NOT reliably reproducible on demand — a rare intermittent collab merge race; GRT Alpha authored the same way did not duplicate. **Evidence:** Real, durable git artifact: history of 'GRT Beta 90706.md' = dfe4a45 headings=0 -> dd046cd headings=1 -> 6231be4 headings=2; `git diff dd046cd 6231be4` = '1 file changed, 15 insertions(+)' inserting a verbatim duplicate block (# Heading, intro, > [!info] callout, ```const v=42, nested list). Both commits authored by server 'Docmost Sync' with NO UI edit between, so the export itself duplicated the body. Verifier could not reproduce a FRESH duplication (controlled retries stayed at 1 copy via the page API) — hence 'likely', not 'verified' — but the git history is permanent evidence and the same duplication is independently verified at the UI/collab layer (see 'Editor content duplicated' in the UI findings). Same collab/Yjs merge-on-reconnect race class, amplified by the sync export. ### [low] Git smart-HTTP host + clone working with auth enforced (verified working, not a defect) _(conf: verified, by recon)_ **Repro:** curl info/refs with and without basic-auth; then git clone http://admin%40test.local:gitmostdev2026@localhost:3000/git/<spaceId>.git **Evidence:** GET /git/<spaceId>.git/info/refs?service=git-upload-pack -> 200 WITH basic-auth, 401 WITHOUT (correct enforcement); body is a valid git smart-HTTP pkt-line stream. git clone exited 0 and produced 209 .md files (one per page; the vault grew during the session as testers added pages). Each file has '---\ngitmost_id:<uuid>\n---' frontmatter then real markdown (headings, bold/italic, bullet/numbered lists, '- [ ]' tasks, blockquotes, '> [!info]' callouts, fenced ```js code). git log shows 'docmost: sync N page(s)' commits. Healthy baseline for the git-sync feature. ## gitmost-ui findings (8) ### [high] Silent body-content data loss when typing into a freshly-created page before collab sync _(conf: verified, by editor-content-tester)_ **Repro:** In space General click '+' (Create page). As soon as the blank editor appears (type within ~2-3s, before the Hocuspocus/Yjs provider fires 'synced'), click into the body and type text. Wait a few seconds, reload (or open in a fresh browser). Body is empty again ('Write anything. Enter / for commands'); typed text is gone, no error. Workaround: wait ~10s before typing, OR reload once before typing. **Evidence:** Independently reproduced, deterministic across 2 verifier runs. Fresh page + 3s wait: immediate editor DOM showed typed text (imm=2) but after reload DOM=0 AND server-stored content via POST /api/pages/info = 0 nodes, with 4xx/5xx=[] and console-errors=0 (fully silent). Threshold sweep: 3s=LOST, 6s=partial, 10s=OK; settle-reload-before-typing=OK. Control: editing an existing/synced page persists perfectly (imm->reloadDOM->API all =1), scoping the bug to fresh pages only. Mechanism: editor is editable before the provider fires 'synced', so the server's initial empty doc clobbers the early local edits. Screenshots: scratchpad/ec/loss_0_typed.png, loss_0_reload.png; scripts iv_repro.py, iv_login.py. ### [medium] Initial keystrokes (and leading title characters) dropped when typing into a just-created page before the collaborative editor connects _(conf: verified, by git-roundtrip-tester)_ **Repro:** Click 'Create page' and immediately type a title + heading/paragraph into the body before the collab WebSocket connects. Leading keystrokes are silently discarded while the editor still shows the inviting 'Write anything' placeholder. **Evidence:** Decisive A/B (vr_ab.py): 'body contenteditable right after create: false (+8.29s)'. PHASE-A text typed while read-only -> body stayed empty, keystrokes dropped. Body became editable at +10.88s (coincides with ws collab connect); PHASE-B text typed after editable was captured. After reload only the AFTERREADY text persisted. Reproduced across ~10 runs; connect time varied +2s..+21s. Corroborating: sidebar littered with 'HEADINGFAST'/'INGFAST'/'NGFAST'/'ADINGFAST' (same title with 0/2/3/4 leading chars eaten) and the pre-existing truncated titles 'ent Test'/'ntent Test'/'ntent Te' (from 'Content Test') plus clone filenames matching — the same race also eats leading title keystrokes during the button->title focus transition. Note: /api/pages/info returns content:null for collab-stored bodies, so reload-DOM is the reliable oracle. Closely related to the high-severity fresh-page body-loss finding (same collab-connect race). ### [medium] Editor content duplicated (stored twice) in the persisted collab doc after reload on a fresh page _(conf: verified, by editor-content-tester)_ **Repro:** Type a multi-block rich document (H1, H2, bold/italic paragraph, bullet list w/ nested item, numbered list, task list) into a fresh page, then reload. Intermittent (a clean create-close-reopen-type flow did NOT always duplicate) — a race in the same collab-sync neighborhood as the loss bug. **Evidence:** Verifier reproduced from scratch. Backend storage of the originally-reported page (pageId ZQ4LgGQVRt) still doubled: POST /api/pages/info -> content contains the entire block sequence TWICE with DISTINCT node ids (two 'Heading One' obqbpkxwukbo + ippnwcwkspcr, two 'Heading Two', doubled bullet/ordered/task lists) — distinct ids prove two real stored copies, not a DOM artifact. Live repro: freshly typed multi-block doc rendered twice after reload (iv_dup/d2_B1_ctx.png) and survived a fully fresh browser context (d2_B1_fresh0.png), so it lives in the server-side Yjs/Hocuspocus doc shared by all clients. In the verifier run leading blocks were LOST while the tail was DUPLICATED — same reload-time collab race as the loss bug. ### [medium] "New chat" is a no-op after a failed first turn — stale failed message and red error banner persist _(conf: verified, by ai-chat-ui)_ **Repro:** Open AI chat (fresh, no chat selected). Send a message; with no provider it fails with the red 'AI provider not configured' banner and the user bubble stays. Click header 'New chat'. Expected: clean empty chat. Actual: old user message AND red banner remain; nothing resets. Close+reopen the window DOES clear it. **Evidence:** Reproduced on the live stand (aichat_refine.py): after Send +9s user msg present + banner in DOM; after New chat x1 and x2 both still present (persistent no-op); after Close+Reopen both gone. Screenshot R2_after_newchat_settled.png. Root cause confirmed in source: ChatThread key={threadKey} (ai-chat-window.tsx:624); reconciler use-chat-session.ts:216 `if (activeChatId !== thread.chatId)` only remounts on activeChatId change; after a failed first turn no id was adopted so both are null, startNewChat (ai-chat-window.tsx:216) sets activeChatId=null (no-op), threadKey never changes, useChat keeps the optimistic message + error. Comment at ai-chat-window.tsx:210-215 acknowledges the no-op path but only disarms the adoption fallback — it never resets the visible thread. (Surfaced via the env's unconfigured-provider 503, but the reset defect is real and provider-independent.) ### [low] Trash page renders a blank table with no loading indicator for ~2-5s on fresh reload / direct URL load _(conf: verified, by tree-nav)_ **Repro:** Open /s/general/trash directly or hard-reload (F5). Intermittently the route shows only the 'Trash' heading + 30-day banner (or nothing at all early) with NO table/rows and NO spinner/skeleton, then rows pop in. In-app navigation (Space menu -> Trash) renders instantly. **Evidence:** 15x direct-reload loop: rows first appear ~2-5s late, and on 4/15 reloads the table stayed empty the entire 6s sample window. anyLoader=False on ALL 15 reloads (no class~=loader|skeleton|spinner|loading, no [role=progressbar]/[aria-busy]). At t=1s the whole route is unmounted (main.innerText=''), then heading+banner+rows appear together. In-app pushState nav firstRowAt=0.05s. Data integrity fine: /api/pages/trash returns HTTP 200 every time. Root cause: slow first-paint with zero loading state, heavily amplified by Vite dev-mode cold module compilation (production build would be far faster). Low severity; a user reloading mid-load could momentarily misread it as 'trash is empty'. Screenshots stuck_empty.png, probe0.png. ### [low] Ambient jotai 'atomFamily is deprecated' console warning on every authenticated page _(conf: verified, by recon)_ **Repro:** Load any authenticated page and read the browser console. **Evidence:** Console WARNING (not error): '[DEPRECATED] atomFamily is deprecated and will be removed in v3. Please use the `jotai-family` package instead'. Appears on initial /home load and again after full reload (reload match count 1), so it is ambient page-load noise, not action-triggered. No crash, no blank page. Benign library deprecation noise — flagged so it is not mistaken for an action-triggered failure during charters. ### [low] Git-sync space-settings UI exposes only two toggles (no remote URL / branch / sync-now / status) — map fact, not a defect _(conf: verified, by recon)_ **Repro:** Space (/s/general) -> Space menu -> Space settings -> Settings tab; scroll the git-sync section. **Evidence:** The git-sync section contains exactly two controls: switch 'Enable Git sync' (ON) and switch 'Auto-merge conflicts on push' (OFF), with explanatory copy. DOM dump of the modal returned only these two switches; the only text inputs are Name/Slug/Description and the Members search box. NO 'Sync now' trigger, NO remote-URL field, NO branch field, NO last-sync timestamp/status. Screenshot 70_settings_tab.png. Consequence for QA: sync state must be observed via the git smart-HTTP host / a fresh clone, not the settings panel. ### [low] Admin 'Analytics / tracker' setting injects verbatim markup into <head> of public share pages (intentional, admin-gated stored-script surface) _(conf: verified, by share-search-settings)_ **Repro:** /settings/workspace -> 'Analytics / tracker' section -> set a snippet -> load a public /share/:shareId/p/:pageSlug page. **Evidence:** Verifier set a probe '<script>/*...$&$$ `'...*/window.__probe=1;</script>' via admin POST /api/workspace/update (200, persisted verbatim incl. regex-special $&$$). Anonymous GET of the public share SEO route returned HTTP 200 with the snippet injected byte-for-byte before </head> (inject-tracker-head.util.ts uses a function replacer, deliberately no escaping; single injection site share-seo.controller.ts on the public route only). Write is admin-gated server-side (CASL Manage Settings -> ForbiddenException for a MEMBER). Resetting trackerHead='' removed it. Working as designed (GA/Yandex.Metrika equivalent), NOT a vulnerability — flagged as a stored-script design surface worth awareness, confirmed restricted to trusted admins. ## ✅ False positives (убиты verifier-проходом) (5) - Repeated React 'flushSync was called from inside a lifecycle method' warnings during block insertion (reported by editor-content-tester). Verifier verdict: SPECULATIVE/not-a-bug. The exact warning reproduced only intermittently (counts 2,8,0,0 across 4 runs) and was NOT tied to insertion — in the decisive run with console-ordered markers it fired only at editor MOUNT (right after 'ws connected', before any slash-menu use); the subsequent insertions added none. It is a known benign React DEV-ONLY warning from @tiptap/react EditorContent (PureEditorContent), stripped from production builds. Blocks render and persist; no crash. The 'repeatedly while inserting blocks' causation does not hold. - Code block drops the first few typed characters at creation, e.g. 'const x = 42;' -> 'x = 42;' (reported by editor-content-tester). Verifier verdict: NOT A BUG. The created element is <pre><code class="language-const">x = 42;</code></pre> — 'const' was consumed as the fenced-code-block LANGUAGE info-string (the standard ```js/```python Markdown shortcut via tiptap input rule /^```([a-z]+)?[\s\n]$/), not dropped. Disconfirming controls: ```+Enter then type, and ```+space-first then type, both preserve 'const x = 42;' fully; deterministic even at slow 80ms/char typing, so NOT the speculated typing-speed input-capture race. Expected behavior. - Authenticated global search shows no empty-state (blank panel) for zero results, unlike public search (reported by share-search-settings). Verifier verdict: COULD NOT REPRODUCE. After the query resolves the authenticated spotlight renders 'No results found...' exactly like the public share search (auth_empty.png; aria-live status 'No results found'). Source search-spotlight.tsx:104-106 renders <Spotlight.Empty> when query.length>0 && !isLoading && results===0. The only difference vs the share variant is an extra `!isLoading` guard, so the auth panel is momentarily blank only during the sub-second in-flight loading window — the original tester almost certainly screenshotted that transient loading frame. Steady state is correct and consistent; at most a cosmetic sub-second flicker, not a missing empty-state. - Debounced-save race: expanded tree state occasionally reverts to collapsed after a move-then-immediate-reload (reported by tree-nav, originally self-tagged speculative). Verifier verdict: SPECULATIVE/inconclusive — could not establish a reliable repro; in dedicated tests manual expand+reload and manual collapse+reload both persisted correctly. Observed once and confounded by auto-expand-on-drop (not a user toggle). Not a confirmed defect. - Different-paragraph concurrent edits merge cleanly and both survive live without reload — 'verified working' (reported by dual-edit). This is a GOOD-NEWS claim, not a bug; flagged here only because the verifier COULD NOT REPRODUCE it end-to-end: the outbound DB->git sync was wedged on the contended shared stand (HEAD frozen at an external 'gitfirst' commit for 9+ min, bodies stuck in DB, never exported), so the clean-merge roundtrip was neither confirmable nor refutable during the verifier's window. The merge logic is NOT declared broken — just unconfirmed under a stalled-sync condition (plausibly transient contention from parallel agents). --- # Подробный мета-отчёт # gitmost git-sync — QA Synthesis Meta-Report **Stand:** gitmost (Docmost fork) — UI http://localhost:5173, API + git smart-HTTP http://localhost:3000, git-sync space "General" (spaceId 019f01fd-3350-7cc9-9446-e20fb81e6c1d). AI provider intentionally NOT configured. **Methodology:** web-test-orchestrator skill — recon → mapping → charters → execution → FEW HICCUPPS oracle evaluation → independent Doer/Verifier pass → this synthesis. Hard rules: evidence-before-claim, disconfirm-by-default, reload-to-test-persistence. ## Headline numbers - **Raw findings submitted:** 24 (across 7 agents). - **Confirmed REAL after independent verification:** 14 distinct items (8 gitmost-ui + 6 git-sync), after dedup/merging. - **False positives / not-a-bug / unconfirmed:** 5 (see list). - **Env-limitations (correctly handled, not bugs):** AI-chat send 503 + AI settings page render — both degrade gracefully (friendly banner, no crash/blank). - **Top risk:** 1 HIGH (fresh-page silent body-content loss) + 1 silent git-wins data-loss (medium) — both are SILENT data loss with no user-facing signal, the most dangerous class. ## Agents that ran and how each stage went ### Stage 1 — Recon (1 agent) Established a clean baseline: all main authenticated screens load with zero console errors / zero HTTP >=400 / no blank pages; ProseMirror mounts and survives reload. Mapped the git-sync settings surface (only two toggles), confirmed the git smart-HTTP host + clone works with auth enforced, and pre-flagged two pieces of ambient noise (jotai atomFamily deprecation warning; AI 503 env limit) so later charters wouldn't misread them. Recon was high-value: it produced the map facts that made later git-sync charters observe sync state via clone, not the (insufficient) settings panel. The only verifier hiccup was self-inflicted rate-limiting (429 on repeated logins) — a tooling artifact, not a product bug. ### Stage 2 — Six slice testers | Agent | Coverage | Real found | FP/not-a-bug | |---|---|---|---| | **editor-content-tester** | Fresh-page typing, rich blocks, slash menu, persistence | Silent body loss (HIGH), content duplication (MED) | flushSync-on-insert (benign DEV warning), code-block 'first chars dropped' (it's the markdown language info-string) | | **tree-nav** | Page tree, drag-move, trash, expand-state persistence | Trash blank-table slow first-paint (LOW) | Expand-state-reverts race (inconclusive) | | **share-search-settings** | Public share, search empty-state, workspace/AI settings | Analytics/tracker head-injection design surface (LOW) | Auth search 'no empty-state' (transient loading frame, steady-state correct) | | **ai-chat-ui** | AI chat panel, send, New-chat reset | 'New chat' no-op after failed first turn (MED) | — (the 503 itself is the env limit) | | **git-roundtrip-tester** | UI-author → git export → clone roundtrip | git push 503 during sync (MED), early-keystroke/title drop (MED), intermittent body duplication (MED) | — | | **dual-edit** | Concurrent UI-edit vs git-push conflict matrix | Same-para git-wins silent overwrite (MED), push-bounce 503 (MED), outbound lag/empty-first-commit (LOW), asymmetric rejection (LOW) | 'Different-para clean merge works' (good-news claim, unconfirmable while sync wedged) | ### Stage 3 — Independent verifiers (Doer/Verifier pass) The verifier pass did exactly its job and is the most valuable stage in this run: it **upgraded** strong findings with decisive root-cause evidence (source line refs for the New-chat no-op and the git-wins merge; GIT_CURL_VERBOSE wire capture proving the server-issued 503+Retry-After), and it **killed 5 weak findings** that would otherwise have shipped as bugs — notably the "code block drops chars" (actually correct Markdown behavior) and the "auth search has no empty-state" (a transient loading frame). Two verdicts came back with placeholder evidence ("e") for the title-truncation and tree-expand findings; I treated title-truncation as confirmed because it is the same robustly-verified collab-connect race as the keystroke-drop finding, and demoted the tree-expand one to false-positive per its speculative twin verdict. ## Dedup / merge decisions - **Fresh-page early-typing loss family:** kept the editor-content-tester body-loss as the HIGH headline, merged the git-roundtrip "keystroke drop before connect" + title-truncation into one MEDIUM UI finding (same collab-connect race, distinct mechanism: editor held `contenteditable=false` / unsynced empty doc clobbers early edits). - **Concurrent-push 503:** git-roundtrip-tester and dual-edit reported the same phenomenon (per-space Redis lock → application-level 503 + Retry-After) — merged into one git-sync finding; verifier confirmed it's broader than reported (the background poll, not just human edits, holds the lock). - **Body duplication:** the UI-layer dup (editor-content-tester, VERIFIED) and the git-sync-cycle dup (git-roundtrip, speculative but with a permanent git-diff artifact) are the same root-cause class; kept as one UI finding + one git-sync finding (the latter marked `likely`). ## Coverage gaps (honest) 1. **AI features are almost entirely untested** because no provider is configured. We verified graceful degradation (503 → friendly banner, no crash) and one real reset bug, but token/context badge, streaming, tool-calls, agent roles, MCP, embeddings, and voice-STT were never exercised. This is an ENV limitation, not a product verdict — treat AI as UNTESTED, not "passing". 2. **Sync stalled mid-run.** A verifier observed outbound DB→git sync wedged for 9+ minutes after an external push on this contended shared stand, which blocked confirmation of the good-news "different-paragraph clean merge" path and the optimistic "body lands ~10-15s later" claim. The wedge itself (an external push appears to stall the sync loop) is a plausible defect we could NOT pin down — worth a dedicated, isolated repro. 3. **Multi-agent contention polluted timing.** Several testers note the stand was being hammered by parallel agents (pages appearing live, vault growing 157→209 files), inflating the 503 rate (44% / 70%) and making some races look more frequent than a normal single-user deployment would see. Severities account for this. 4. **Vite dev-mode artifacts.** The trash blank-table and some first-paint slowness are amplified by dev-mode cold module compilation; a production-build pass is needed to confirm real-world severity. 5. **No security depth on the tracker-injection surface** beyond confirming admin-gating and verbatim injection — we did not attempt XSS/CSP bypass scenarios on public share pages. ## Concrete improvement suggestions for this QA process / skill 1. **Add a collab-readiness oracle.** The two worst bugs (silent body loss, keystroke drop) both stem from the editor being interactable before the Yjs/Hocuspocus provider fires `synced`/connects. A reusable probe should: create a page, assert `contenteditable` + provider-synced state, and only then type — and as a CHARTER, deliberately type during the unready window to catch the loss. Make "type-before-ready then reload" a standing regression check. 2. **Don't trust `/api/pages/info` as the persistence oracle for collab bodies** — it returns `content:null` for ydoc-stored bodies. Codify "reload-DOM is the source of truth for collab content; API snapshot is debounced and lags" in the skill's oracle notes, so testers stop mis-scoring loss/dup. 3. **Distinguish "transient loading frame" from "missing state".** Two FPs (auth search empty-state, trash blank table) came from screenshotting an in-flight frame. Add a rule: for any "missing UI element" claim, sample the DOM repeatedly until network-idle / the relevant XHR resolves before asserting absence. 4. **Encode Markdown/editor semantics to pre-empt not-a-bug reports.** The "code block drops chars" FP was standard fenced-code language info-string behavior. A short "known editor behaviors" crib (language fences, input-rule transforms, smart-paste) would have caught it before it reached a verifier. 5. **Isolate git-sync timing tests from concurrent agents.** Race-rate numbers (503 frequency, lag) are meaningless on a contended stand. Provide a single-tenant window or a dedicated space per agent so severities reflect a normal deployment, and instrument the Redis lock + sync cadence directly rather than inferring from push outcomes. 6. **Add an explicit "silent data loss" oracle tier.** Three of the most serious findings (fresh-page loss, git-wins overwrite, body dup) produced ZERO console/HTTP signals — the cheap signals missed them entirely; only reload-persistence + backend-storage diffing caught them. The skill should mandate a persistence/round-trip diff for any create/edit/merge charter, not just signal-watching. 7. **Tag env-limitation vs untested explicitly in the final report** so an owner doesn't read "AI handled gracefully" as "AI works". We did this; make it a required field. 8. **Verifier discipline paid off — keep it, but require non-empty evidence.** Two verdicts shipped with placeholder "e" evidence. Enforce a minimum-evidence gate (artifact path or captured output) before a verdict is accepted, otherwise demote to speculative. 🤖 web-test-orchestrator (multi-agent QA), повторный прогон по PR #119 (после фиксов)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vvzvlad/gitmost#223