fix(gbrain-source-wireup): first sync targets the registered source, not --repo

The wireup registered a federated source by id, then ran 'gbrain sync --repo $WORKTREE' — which resolves against the brain's DEFAULT source and (on gbrain 0.46.x) rewrites that source's local_path anchor to our worktree. Net effect: the user's primary knowledge source silently repointed at the gstack brain worktree while the just-registered source got zero pages, and pages_synced still reported success. The sync now targets the registered id ('gbrain sync --source $id', the same form the repo's own troubleshooting documents). Because the script's stated floor is gbrain >= 0.18.0 and nothing proves --source exists there, support is probed via 'gbrain sync --help' first: an older gbrain keeps the wrong-but-working --repo call with an upgrade warning instead of converting it into a hard failure. The probe sits after the GSTACK_BRAIN_NO_SYNC early-exit and is unreachable in --probe mode.

Regression tests (fail on v1.68.3.0): a no-skip sync case asserting the call log shows 'sync --source gstack-brain-<id>' and never 'sync --repo', and an old-gbrain fallback case (fake sync --help without --source) asserting --repo plus the upgrade warning.

Fixes #2662
This commit is contained in:
Garry Tan
2026-08-22 02:04:02 +00:00
parent 8b426b3050
commit ffa7b1f452
2 changed files with 48 additions and 1 deletions
+14 -1
View File
@@ -332,8 +332,21 @@ do_wireup() {
exit 0
fi
# #2662: `sync --repo <path>` resolves against the brain's DEFAULT source and
# can silently repoint that source's local_path anchor at our worktree while
# the source registered above gets nothing. Target the registered source by
# id. `--source` support is probed first (the documented floor is gbrain >=
# 0.18.0 and nothing proves the flag exists there): an older gbrain keeps the
# wrong-but-working --repo call with an upgrade warning, never a hard failure.
local sync_out sync_redacted
sync_out=$(gbrain sync --repo "$WORKTREE" 2>&1) || {
local -a sync_cmd
if gbrain sync --help 2>/dev/null | grep -q -- '--source'; then
sync_cmd=(gbrain sync --source "$id")
else
warn "this gbrain's sync lacks --source; falling back to 'sync --repo' (upgrade gbrain so the sync targets source $id directly — #2662)"
sync_cmd=(gbrain sync --repo "$WORKTREE")
fi
sync_out=$("${sync_cmd[@]}" 2>&1) || {
# Redact any postgres:// URLs from the error message in case gbrain logged
# a connection error containing the full DSN with password. The user sees
# "***REDACTED***" instead of credentials in their stderr or any log.
+34
View File
@@ -42,10 +42,12 @@ let gbrainStateFile: string;
function makeFakeGbrain(opts: {
version?: string | null; // null = "binary missing" (don't write the file)
syncFails?: boolean;
syncHelpNoSource?: boolean; // simulate an older gbrain whose sync lacks --source
}) {
const version = opts.version ?? '0.18.2';
if (version === null) return; // simulate missing binary by NOT writing one
const syncFails = opts.syncFails ?? false;
const syncHelpNoSource = opts.syncHelpNoSource ?? false;
// Stub gbrain reads/writes state from a JSON file. Fields:
// sources: [{id, local_path, federated}]
@@ -109,6 +111,13 @@ json.dump(state, open('$STATE','w'), indent=2)
fi
# sync --repo <p> → records, optionally fails
# sync --help → advertise flags (the wireup probes this before choosing the
# sync form; the default fake mirrors a current gbrain, which HAS --source)
if [ "$1" = "sync" ] && [ "$2" = "--help" ]; then
echo "Usage: gbrain sync [--repo <path>]${syncHelpNoSource ? '' : ' [--source <id>]'}"
exit 0
fi
if [ "$1" = "sync" ]; then
${syncFails ? 'echo "sync failed: connection error" >&2; exit 1' : 'echo "1 page imported"; exit 0'}
fi
@@ -193,6 +202,31 @@ describe('gstack-gbrain-source-wireup — wireup mode', () => {
expect(state.sources[0].federated).toBe(true);
});
test('the real sync targets the REGISTERED source, never --repo (#2662)', () => {
// `sync --repo <path>` resolves against the brain's DEFAULT source and can
// silently repoint its local_path anchor at our worktree. This case runs
// WITHOUT GSTACK_BRAIN_NO_SYNC — the skip-mode cases never reach the sync,
// so asserting the sync argv there would be vacuous.
setupGstackRepo('git@github.com:user/gstack-brain-user.git');
makeFakeGbrain({});
const r = run([]);
expect(r.status).toBe(0);
const calls = gbrainCalls();
expect(calls.some((c) => c.startsWith('gbrain sync --source gstack-brain-user'))).toBe(true);
expect(calls.some((c) => c.includes('sync --repo'))).toBe(false);
});
test('older gbrain without sync --source: falls back to --repo with an upgrade warning', () => {
setupGstackRepo('git@github.com:user/gstack-brain-user.git');
makeFakeGbrain({ syncHelpNoSource: true });
const r = run([]);
expect(r.status).toBe(0);
const calls = gbrainCalls();
expect(calls.some((c) => c.startsWith('gbrain sync --repo'))).toBe(true);
expect(calls.some((c) => c.includes('sync --source '))).toBe(false);
expect(r.stderr).toContain('#2662');
});
test('idempotent re-run after success: no new sources add call', () => {
setupGstackRepo('git@github.com:user/gstack-brain-user.git');
makeFakeGbrain({});