Repo-validated bug / incident ledger as of 2026-05-27.
- Status: fixed
- Failure shape: a device-local edit made while YAOS was disabled could be lost after re-enable
- Representative failing evidence:
qa-runs/2026-05-15T22-18-56-issue-22-disable-reenable-disk-wins-A/result.json- error:
DATA LOSS: B's edit made while YAOS was disabled was LOST after re-enable
- Representative passing evidence:
qa-runs/2026-05-15T22-41-29-issue-22-disable-reenable-disk-wins-A/result.jsonqa-runs/2026-05-15T23-23-02-issue-22-disable-reenable-local-only-A/run.logqa-runs/2026-05-15T23-20-55-issue-22-disable-reenable-concurrent-A/run.log
- Current understanding:
- disk-wins main-file behavior now preserves the offline disk edit
- conflicting CRDT content is redirected into an artifact instead of overwriting the main file
- local-only and concurrent variants also have passing representative runs
- Status: fixed 2026-05-27
- Failure shape: when Obsidian was killed or suspended before YAOS persisted the disk-index
baseline for a file, re-enable triggered
missing-baseline → winner: crdt, silently demoting the user's local disk edit to a conflict artifact while the remote CRDT content became the main file. For note-taking users this felt like "turning YAOS back on lost my edits," even though the edit was technically preserved in the artifact. - This is a different code path from the clean-disable case above. The clean-disable path
persists the baseline via
teardownSync → flushAllPendingWrites → saveDiskIndexbefore any kill. The cold-kill path bypassesteardownSyncentirely, leavingbaselineHashnull. - Desktop repro:
qa/scripts/repro-missing-baseline-kill.ts- Before fix:
preserve-conflict / missing-baseline / winner: crdt(user edit demoted) - After fix:
preserve-conflict / missing-baseline / winner: disk(user edit wins main file)
- Before fix:
- Fix:
src/sync/closedFileConflict.ts— missing-baseline now uses mtime evidence:- If
diskMtime > lastDiskIndexPersistedAt(file was modified after YAOS last persisted clean state), treat as "edited while inactive" and give disk the main file; CRDT remote content is preserved as a conflict artifact. - Otherwise fall back to the existing
winner: crdtsafe distributed default.
- If
- New persisted field:
_lastDiskIndexPersistedAtwritten todata.jsonon everysaveDiskIndex(). Loaded on startup before the first reconcile. - Known limits of the heuristic (by design, documented in
closedFileConflict.ts):- Global timestamp, not per-file: an unrelated file triggering a save AFTER the target file's mtime can cause CRDT to win even when the user made a local edit.
- mtime coarseness: filesystems with 1-second precision, external editors that preserve mtime, or iCloud/Android document providers may produce unexpected values.
- When either input is absent, falls back to
winner: crdt(safe default preserved).
- Diagnostic fields added to
reconcile.file.decision.datawhenreason: "missing-baseline":missingBaselinePolicy,diskMtime,lastDiskIndexPersistedAt,mtimeEvidence. - iPad proof: still pending. Desktop CDP proves the policy branch. Real-device validation required before closing Issue #22-B fully for mobile.
- Status: fixed in current repo evidence
- Failure shape: passive device could reconnect and keep an older sentinel after sync
- Representative failing evidence:
qa-runs/2026-05-15T22-13-48-issue-22-passive-reconnect-A/result.json- error:
Cycle 2: B still has sentinel after reconnect+sync — stale state
- Representative passing evidence:
qa-runs/2026-05-15T22-15-26-issue-22-passive-reconnect-A/result.jsonqa-runs/2026-05-16T16-35-50-s11a-passive-stale-echo-witness-B/summary.md
- Current understanding:
- the stale resurrection / stale echo class that motivated this thread is covered by later witness runs
- current repo evidence supports convergence without old-state resurrection
- Status: not reproduced as a current failing repo state
- Representative passing evidence:
qa-runs/2026-05-15T21-01-44-issue-22-passive-open-roundtrip-A/result.jsonqa-runs/2026-05-15T21-41-50-issue-22-passive-open-roundtrip-A/result.json
- Remaining caveat:
- original reporter validation is still not evidenced in-repo, so this is covered by harness evidence rather than direct reporter confirmation
- Status: fixed at the server persistence / pathology level
- Failure shape: the server behaved like a live relay when both devices were online but did not durably persist state for later cold-start handoff
- Root cause summary:
- save failures in the old persistence chain could be swallowed, leaving the server apparently healthy but durably stale
- Fix class:
PersistenceCoordinator- immediate checkpoint fallback after append failures
- pending-persistence health semantics
- legacy document migration
- better debug surface for deployment validation
- Current caveat:
- storage-level and pathology validation are strong, but full live provider/client handoff proof is still tracked separately in
engineering/followups.md
- storage-level and pathology validation are strong, but full live provider/client handoff proof is still tracked separately in
- Status: resolved on current main
- Failure shape: editor-bound recovery repeatedly appended content every few seconds instead of converging
- Fix class:
- recovery postconditions
- recovery fingerprinting / quarantine
- better recovery flight events
- Current understanding:
- forced local-only and open-idle recovery branches converge
- the natural repeated-anchor symptom test no longer reproduces the growth loop
- Status: fixed 2026-05-28
- Failure shape: while a Markdown file was open in the editor and the user
was typing, the controller's
bound-file-local-only-divergencebranch ran every ~2.36s. CRDT length and disk length each grew by exactly +5 chars per cycle. Each cycle satisfied its postcondition (matchesExpected: true), applied an apply-diff underORIGIN_DISK_SYNC_RECOVER_BOUND, and callededitorBindings.repair(). The witness fireddevice.witness.diverged~2 seconds after eachrecovery.apply.donebecause the next typing burst had landed disk in the meantime. Loops at ~2.36s cadence forever while typing continues. - Existing protections did not catch it:
BOUND_RECOVERY_LOCK_MS(1500ms) lock expires before the next 2.36s cycle.shouldQuarantineRepeatedRecoveryis fingerprint-keyed; each cycle's(prev, next)lengths are unique and prefixes change, socountnever reaches the threshold of 3.OPEN_FILE_EXTERNAL_EDIT_IDLE_GRACE_MS(1200ms) idle guard exists in thecrdtOnlybranch but not inlocalOnly.
- Real-device evidence:
~/temenos/.obsidian/plugins/yaos/flight-logs/2026-05-27/boot-wL7i012vR4mGXA-1.ndjson- pathId
p:476818d2ecba90d4e95e2a0c4f3ad1eb - timeline reproducible locally via
qa/scripts/issue22b-loop-summary.mjs(script lives outside the repo becauseqa/is gitignored; rebuild from the trace if needed)
- Fix:
OPEN_FILE_LOCAL_ONLY_RECOVERY_IDLE_MS = 3000ms. New idle guard added to the localOnly branch, symmetric to the crdtOnly branch's existing 1200ms guard. Threshold is intentionally longer because localOnly is the typing-cadence amplifier shape; quenching it requires a window longer than a typical human typing burst. Emitsrecovery.skippedwith reasonrecent-editor-activity-local-only.- Monotonic-growth amplification quarantine. New per-path detector
independent of fingerprint identity. Trips when 3 consecutive
bound-file-local-only-divergencerecoveries within 15s exhibit non-decreasing prevLen and nextLen with strictly positive deltas AND end values strictly larger than start values. Emits new flight kindrecovery.amplification.quarantined(taxonomy bumped to 10) plus the existingrecovery.loop.detected. - Pauses (idle-guard skip, crdt-current no-op, recovery-lock skip) reset the amplification history. Legitimate one-shot recoveries do not count toward future quarantines.
- Reviewer follow-ups (2026-05-28):
- Binding-health-conditional repair. The localOnly per-view repair
loop now calls
editorBindings.repair()only whenclassifyBindingHealthreturns unhealthy (cmMatches === false,hasSyncFacet === false,yTextMatchesExpected === false, orawarenessMatchesProvider === false). Healthy bindings skip the compartment reconfigure. Removes the per-cycle jitter that biased the autosave-vs-y-sync race. - Writer attribution on
disk.modify.observed. New fieldswriterGuess("yaos-write" | "external" | "unknown"),suppressWindowActive,lastDiskWriteOkAtMs,msSinceLastDiskWriteOk. Future RCAs no longer have to infer writer identity from absence ofdisk.write.ok. - Duplicate
recovery.decisionrecording fixed. TherecordFlightPathEventhelper inmain.tsnow routesrecovery.decisionthroughreserveAndRecordPathonly; the earlierrecordPathcall is skipped for that kind. - User-visible Notice on amplification quarantine. Throttled to one per 60s; suppressed firings are counted in the next surfaced notice.
- Binding-health-conditional repair. The localOnly per-view repair
loop now calls
- Spec:
.kiro/specs/editor-bound-localonly-amplifier-guard/requirements.md - Regression:
tests/controller-recovery-orchestration-amplifier.ts(5 deterministic Node scenarios, 48 assertions innpm run test:regressions). - iPad proof: still pending. Desktop coverage proves the predicate, the flight kind, and the timing. Real-device validation required before closing this variant fully for mobile.
- Status: fixed and guarded
- Failure shape: stale compiled
.jsfiles undersrc/caused jiti to load a second Yjs instance, creating misleading failures in seven suites - Representative evidence:
scripts/guard-no-src-js-artifacts.mjspackage.json
- Current understanding:
- this was tooling pollution, not a semantic product regression
- regression entrypoint now hard-fails if these artifacts reappear
- Status: open
- Why still open:
- repo evidence shows harness reproduction coverage and later passes
- repo does not show the original reporter validating a build
- Status: open
- Why still open:
qa-runs/s12a-three-device-pass/summary.mdproves passive quorum on a pre-existing hash- it does not prove three devices converging after a new edit during the run
- Status: open
- Why still open:
- desktop
s12cexists engineering/layer4-harness-status.mdstill lists real-devices12cas not proven
- desktop
- Status: open design / follow-up work
- Primary source:
engineering/followups.md
- Note:
- this is real design debt, but it is not the same thing as the stale seven-suite failure story that was once mistaken for a product regression cluster
- Status: re-check needed
- Caveat:
- a live
npm run test:regressionsrun still emitted at least one duplicate-Yjs warning, so the thread may be reduced rather than fully gone
- a live