fix(brain-sync): throttle + bound the detector push; empty-queue fast path

Review-army findings on the #2549 detector. (1) The preamble runs --once at
every skill boundary, so an unthrottled retry paid a full network push
attempt per boundary in exactly the steady states it targets (offline,
broken auth) — a captive-portal push can block 30-75s against the header's
"<1s when idle" promise. Attempts now stamp .brain-last-push-attempt and
retry at most every 10 minutes; the push never prompts (GIT_TERMINAL_PROMPT=0)
and bounds stalled transfers via git's low-speed limits (portable — stock
macOS has no timeout binary). (2) Author-scoped: only gstack-brain-sync's own
commits retry; a user's manual commit in ~/.gstack rides along on real drains
as before, never auto-published by the detector. (3) Empty-queue fast path
exits before the compute/rewrite python spawns — the steady state is now
cheaper than the pre-wave truncation code. (4) The queue rewrite warns on
failure instead of silently letting the status claim a drain that didn't
happen, counts held unparseable lines, and collapses duplicate lines on
rewrite. Throttle + delivery matrix cases added (37/37).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-16 13:06:36 -07:00
co-authored by Claude Fable 5
parent 8a4f4dce1c
commit 4bc5b4caf3
2 changed files with 94 additions and 7 deletions
+45 -1
View File
@@ -596,7 +596,51 @@ describe('#2549 queue integrity', () => {
fs.chmodSync(path.join(tmpHome, 'security'), 0o700);
}
// Receipts healthy: detector delivers.
// Receipts healthy: detector delivers. The refused attempt above stamped
// the 10-minute throttle (deliberately — refusals must not busy-loop the
// network at every skill boundary), so model the interval passing.
fs.writeFileSync(path.join(tmpHome, '.brain-last-push-attempt'), '0');
expect(run(['gstack-brain-sync', '--once']).status).toBe(0);
expect(git(['rev-list', '--count', 'origin/main..HEAD']).stdout.trim()).toBe('0');
});
test('detector attempts are throttled to one per interval', () => {
initWithMode('full');
fs.mkdirSync(path.join(tmpHome, 'projects', 'p'), { recursive: true });
fs.writeFileSync(path.join(tmpHome, 'projects/p/learnings.jsonl'), '{"skill":"a","ts":"2026-01-01T00:00:00Z"}\n');
run(['gstack-brain-enqueue', 'projects/p/learnings.jsonl']);
expect(run(['gstack-brain-sync', '--once']).status).toBe(0);
// Strand a commit behind a rejecting remote.
const hook = path.join(bareRemote, 'hooks', 'pre-receive');
fs.writeFileSync(hook, '#!/bin/sh\nexit 1\n');
fs.chmodSync(hook, 0o755);
fs.appendFileSync(path.join(tmpHome, 'projects/p/learnings.jsonl'), '{"skill":"b","ts":"2026-01-02T00:00:00Z"}\n');
run(['gstack-brain-enqueue', 'projects/p/learnings.jsonl']);
expect(run(['gstack-brain-sync', '--once']).status).toBe(0);
fs.rmSync(hook);
// First empty-queue run: detector attempts (stamps the throttle), pushes.
expect(run(['gstack-brain-sync', '--once']).status).toBe(0);
const stamp1 = fs.readFileSync(path.join(tmpHome, '.brain-last-push-attempt'), 'utf-8');
expect(Number(stamp1)).toBeGreaterThan(0);
expect(git(['rev-list', '--count', 'origin/main..HEAD']).stdout.trim()).toBe('0');
// Strand another; an immediate second run must NOT attempt (stamp fresh).
fs.writeFileSync(hook, '#!/bin/sh\nexit 1\n');
fs.chmodSync(hook, 0o755);
fs.appendFileSync(path.join(tmpHome, 'projects/p/learnings.jsonl'), '{"skill":"c","ts":"2026-01-03T00:00:00Z"}\n');
run(['gstack-brain-enqueue', 'projects/p/learnings.jsonl']);
expect(run(['gstack-brain-sync', '--once']).status).toBe(0);
fs.rmSync(hook);
const stampBefore = fs.readFileSync(path.join(tmpHome, '.brain-last-push-attempt'), 'utf-8');
expect(run(['gstack-brain-sync', '--once']).status).toBe(0);
// Throttled: stamp unchanged, commit still stranded.
expect(fs.readFileSync(path.join(tmpHome, '.brain-last-push-attempt'), 'utf-8')).toBe(stampBefore);
expect(Number(git(['rev-list', '--count', 'origin/main..HEAD']).stdout.trim())).toBeGreaterThan(0);
// Interval passed: delivers.
fs.writeFileSync(path.join(tmpHome, '.brain-last-push-attempt'), '0');
expect(run(['gstack-brain-sync', '--once']).status).toBe(0);
expect(git(['rev-list', '--count', 'origin/main..HEAD']).stdout.trim()).toBe('0');
});