feat: sync UX + statusline/RVF root-cause fixes, full-codebase hardening, docs audit#35
Merged
Merged
Conversation
…strip retired RVF lock scan - output: withProgress elapsed-time ticker (TTY-only, injectable seam) wrapped around every slow managed heal — the ~512 MB brain KB refresh previously sat silent for 4m25s with the prompt frozen - statusline: root cause of the recurring footer wipe identified — @claude-flow/cli's version-stamped helper auto-refresh pristine-copies statusline.cjs on the FIRST ruflo command after an upgrade (observed: daemon start + statusline.cjs + .helpers-version mtimes matching at 12:20:35). fixStatusline now triggers that refresh first (refreshRufloHelpers, subprocess, alsoRefreshGlobal), then injects onto the freshly-stamped copy; helperStampStale lets status flag the armed wipe before it fires. Verified live: footer survives ruflo commands - rvf: strip the version-gated legacy corrupt-lock branch (PR #31's remnant) — dead code now that sync keeps aqe >= 3.12.3 everywhere. Only the oversized backstop (#495 runaway append) remains; the FLVR/SFVR history stays in the module header as a guard against reintroducing bytes-on-disk corruption heuristics - tests: helper-stamp (9), output-progress (5), rvf rewritten for the reduced contract (8) — 125 kit tests green
Adversarial review (Linus/security/Ramsay/Bach lenses, independent agents)
over the branch diff. Everything actionable, remediated with proof:
Ramsay (test quality):
- F1: the refresh-before-inject ordering test used a sentinel-only fake that
passed BOTH orderings; the fake now models the real pristine-copy wipe.
Mutation-verified: moving the refresh after inject turns the test RED
- F2: the ticker never ticked under test — mocked clock now drives the
interval, minute rollover (1m01s), and real caller labels
- F3/L2: refresh child now exits nonzero on rejection AND on upstream's
resolved {blocked} (signature gate) — true means ran-unblocked; hung-child
timeout injectable and tested
- F4: v-prefixed stamp no longer arms a permanent false-stale (latent bug);
empty/garbage stamps pinned stale-by-design (self-correcting)
- F5: cap boundary (=== excluded, +1 flagged), only-.rvf candidacy pinned
Linus (correctness):
- scanRvf per-entry stat guard: a store renamed mid-scan by the aqe daemon
no longer crashes ak status/sync (dangling-symlink test pins the race)
- staleness-oracle divergence: rufloCliVersion gains a createRequire
resolver fallback, and the sync statusline step now also runs on
providers-only plans (any ruflo-invoking heal is a potential wiper)
- statusline step wrapped in withProgress; ticker stdout invariant
documented; stale "corrupt/oversized" wording in x/verify fixed
Bach (claims):
- sync no longer claims victory when the helper stamp is still stale after
the heal (silent upstream-API no-op now warns instead of false success)
- rvf.mjs header rationale corrected: the stripped branch was reachable
(status/--no-upgrade/offline), removed because its false-positive
data-loss risk outweighed its protection — not because it was dead code
Security: clean (empirical: argv-safe subprocess, no shell, upstream
Ed25519 gate preserved; rmSync does not follow symlinks). One LOW
disclosure (alsoRefreshGlobal touches ~/.claude/helpers) → PR body.
137 tests green (was 125 at review start).
Second adversarial wave: 4 independent agents (correctness/security/ coverage/docs-vs-reality) over all 35 source files, templates, bin, docs. Correctness (holistic-linus): - nativeSlots regression metric counted agentdb DIRECTORIES, not native bindings — the native→WASM backslide it exists to catch was invisible, and a benign tree reshape faked an alarm. Now counts native bindings (incl. the aqe slot) - provider scope resolved by cwd-only .git probe: any run from a repo SUBDIR silently wrote ENABLE_*/AQE_LLM_PROVIDER into machine-wide user settings (and undo, run at the root, couldn't find them) while the aqe router/dual-agent gates skipped. All three gates now share a bounded repo-root walk (paths.repoRoot), project files anchor at the root - upsertBlock/stripBlock ran an orphaned BEGIN "to end-of-file" — one corrupt sentinel deleted everything below it in ~/.claude/CLAUDE.md. Missing END now means append-fresh/leave-untouched; syncBlocks takes a one-time .bak like every other writer - brain plugin-version fallback sorted lexically (0.9.0 > 0.10.0) → semver Security (holistic-security; 2 LOW, 4 INFO, rest cleared with evidence): - daemon reap confirms the live pid's cmdline is a ruflo daemon before SIGTERM (pidfile pid-reuse guard; POSIX ps, unconfirmable → never kill) - dashboard rejects non-loopback Host headers (DNS-rebinding read guard) - improvement-eval execSync shell strings → execFileSync argv (the one violation of the exec.mjs binding rule); setup.mjs SQL now uses bound params; .mcp.json deleted only when mcpServers was its sole content Docs-vs-reality (holistic-bach): - --no-security was INERT: documented, persisted, read by nothing. Now gates the aidefence heal in setup, the status security row (info, no fix), and sync's security branch - dead updateHost/hostDrift removed (drift rides versions.mjs driftReport) - KB size claims corrected to ~2 GB (disk truth: 1.9 GB); bin verify list gains harvest; README uninstall row shows the real flags Coverage (holistic-ramsay): daemons.mjs was the only zero-coverage module that kills processes — now pinned (kill selection, dead-pid degradation, pid-reuse guard incl. a live spawned "daemon start" child, sweep parsing). Deferred with eyes open: mcp.mjs deny-rule tests, uninstall.mjs fixture tests, heal.mjs untested tail, dashboard client-esc DOM test. 149 tests green (was 137).
…erts Walked every .md in the repo (37 files; none exceeds 1500 lines, so no decomposition was warranted; docs/archive/* audited as frozen history, content untouched by policy). Accuracy (claims corrected to disk/code truth): - TROUBLESHOOTING: RVF row no longer teaches the debunked FLVR-corruption story — oversized-only guard, aqe >= 3.12.3 self-heal cited (aqe #563), historical note explains .corrupt-<pid> droppings; statusline row now names the real wiper (helper auto-refresh on the first ruflo command after an upgrade) and the refresh-then-inject heal; adds the --no-security row and the harvest verify suite - KB size ~512 MB → ~2 GB everywhere (disk truth: 1.9 GB) — MAINTAINER, README, setup help, heal/ruvnet-brain comments - ruflo-reference stamp 3.28.x/2026-07-14 → 3.32.x/2026-07-18; dead `install.sh` attributions in three templates → ak-era commands - project CLAUDE.md Build & Test: stale single-file command → pnpm test / pnpm run check (the real gates) - archive index: living-docs pointers referenced two files that do not exist (BACKGROUND.md, CONDITIONAL-BLOCKS.md — both are archived); three "survives in bin//shell/" cells updated to v4 paths Readability/labeling: GitHub alert syntax ([!IMPORTANT]/[!TIP]/[!NOTE]/ [!WARNING]) on the load-bearing callouts in README, TROUBLESHOOTING, PROVIDERS (incl. the new repo-root scope note). markdownlint clean. Also: branch renamed feat/sync-progress-statusline-convergence → feat/ux-hardening-docs-audit (PR #34 retargeted); drops the unused cmpVersions import stranded by the wave-2 dead-code removal.
…l on Windows
isDaemonProcess() returned true unconditionally on win32 ("no cheap sync
probe"), so the pid-reuse guard test — which feeds reap() the test runner's
own live pid to prove non-daemons are never killed — SIGTERM'd the runner
itself on windows-latest, failing daemons.test.mjs at file level across the
whole Node matrix (macOS/Linux green).
Fix the cause, not the test: the guard now probes the cmdline on Windows
too, via the same Win32_Process CIM query the process sweep already trusts
(pid numeric-coerced into the filter; unconfirmable → never kill). The
guard test now runs on every platform; only the kill-true path stays
POSIX-only (CI spawn/CIM timing, not a behavior difference).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Three convergence fixes from the 2026-07-18 investigation, then hardened by a 4-lens adversarial review (see below).
1. Sync progress tickers (
withProgress)Every slow managed heal (npm upgrades, the ~512 MB brain KB refresh, native rebuilds, host installs, self-update, statusline) shows a live elapsed-time ticker instead of a frozen prompt.
run()buffers child output by design, so the parent supplies the liveness signal; the thunk-must-not-write-stdout invariant is documented. TTY-only — piped/--jsonoutput is byte-identical to before. Observed trigger: the brain KB refresh sat silent for 4m25s.2. Statusline footer wipe — root cause closed
The recurring footer wipe (AQE/SONA lines vanishing between syncs) is
@claude-flow/cli's version-stamped helper auto-refresh (dist/src/init/helper-refresh.js, run on every ruflo CLI command): after a ruflo upgrade, sync injected the footer onto a stale-stampedstatusline.cjs, and the first ruflo command afterwards pristine-copied it away.fixStatuslinetriggers ruflo's own refresh first (refreshRufloHelpers, subprocess, argv-safe, preserves upstream's Ed25519 signed-manifest gate), then injects onto the freshly-stamped copyhelperStampStaleletsak statusflag the armed wipe; the sync step also runs on providers-only plans (any ruflo-invoking heal is a potential wiper) and warns instead of claiming success if the stamp is still stale after the healEvidence (armed reproduction, live): stamp downgraded to 3.32.2 → the wipe fired within seconds (the statusline render itself is a ruflo invocation) → sync refreshed helpers → stamp 3.32.7 → footer injected → footer survives subsequent ruflo commands. The hermetic ordering test uses a fake refresh that models the pristine-copy wipe and is mutation-verified: moving the refresh after injection turns it red.
Disclosed couplings: (a)
refreshRufloHelpersinvokes a private, unversioned ruflo dist internal (autoRefreshHelpersIfStale); if upstream moves it the fix degrades to a warned no-op, never a silent false success. (b)alsoRefreshGlobal:truealso pristine-refreshes~/.claude/helpers(integrity-gated, mirrors ruflo's own per-command behavior; the kit injects no global footer, so nothing of the kit's is lost).3. RVF: strip the legacy corrupt-lock branch
Follow-up to #31. The branch was reachable (
ak status,sync --no-upgrade, offline) — it is removed not as dead code but because on exactly those paths its false-positive data-loss risk (FLVR is every lock's normal magic; deleting the sibling store) outweighed its marginal protection.scanRvfreduces to the oversized backstop (#495 runaway append) with a per-entry stat guard (daemon renames mid-scan no longer crash status/sync). History preserved in the module header.Adversarial review (4 independent agents: Linus/security/Ramsay/Bach)
rmSyncdoesn't follow symlinks. Refresh-before-inject narrows the statusline attack surface.Tests
137 green, 0 skipped —
tests/kit/helper-stamp.test.mjs(15, incl. throwing/hung/blocked refresh children and malformed stamps),tests/kit/output-progress.test.mjs(8, mocked-clock ticker),tests/kit/rvf.test.mjs(12, boundary + race + only-.rvf candidacy), plus all prior suites.🤖 Generated with Claude Code
Holistic full-source review (wave 2 — all 35 source files, templates, bin, docs)
Four further independent agents swept the ENTIRE codebase (not just this diff). Everything actionable was remediated in
ae2d2be:Correctness —
nativeSlotsregression metric now counts native bindings, not directories (was blind to the native→WASM backslide it's named for); provider scope now walks up to the repo root (a subdir invocation could silently writeENABLE_*/AQE_LLM_PROVIDERmachine-wide with no clean undo); an orphaned CLAUDE.mdBEGINsentinel can no longer truncate everything below it (plus a one-time.bakbefore rewrites); brain plugin-version fallback sorts semver, not lexical.Security — no CRITICAL/HIGH. Daemon reap now verifies the pid's cmdline before SIGTERM (pidfile pid-reuse guard); dashboard rejects non-loopback
Hostheaders (DNS-rebinding); the one shell-string exec violation fixed; SQL bound;.mcp.jsondelete made conservative. Cleared with empirical evidence: dashboard XSS escaping, the "API keys never persisted" promise, all deletion paths, settings backup-first.Docs-vs-reality —
--no-securitywas inert (documented, persisted, read by nothing) and now actually gates setup/status/sync; deadupdateHost/hostDriftremoved; KB size corrected to ~2 GB (disk truth); help/README rows fixed. Verified TRUE against installed ruflo 3.32.7 / aqe 3.13.0 source: every injected CLAUDE.md capability claim, "idempotent sync", "merge-not-clobber backup-first".Coverage —
daemons.mjs(the only untested module that kills processes) now pinned: kill selection, dead-pid degradation, the pid-reuse guard against a live spawneddaemon startchild, sweep parsing. Deferred (tracked, not hidden): mcp.mjs deny-rule tests, uninstall fixture tests, heal.mjs untested tail, dashboard client-esc DOM test.149 tests green (137 → 149).
Documentation audit (final pass — commit 67f0a3f)
Walked all 37 markdown files. Corrected every claim that no longer matched disk/code truth: the debunked FLVR-corruption story in TROUBLESHOOTING (now cites aqe #563 and the real statusline wiper), KB size ~512 MB → ~2 GB (disk truth), stale version stamps, dead
install.shattributions, a stale test command in CLAUDE.md, and archive-index pointers to two nonexistent files. Added GitHub alert labeling ([!IMPORTANT]/[!TIP]/[!NOTE]/[!WARNING]) on load-bearing callouts. No file exceeds 1500 lines, so no decomposition was warranted;docs/archive/*audited as frozen history and left verbatim by policy.pnpm run check(typecheck + eslint + markdownlint + build + tests) fully green.🤖 Generated with Claude Code