fix(paired): auto-provision paired workspace so tribunal rooms get a reviewer on registration
Rooms registered as tribunal had no work_dir and no paired_projects row, so ensurePairedProject() returned null, no paired task was ever created, and only the owner ran — the reviewer/arbiter never fired (web-vstock and 4 other rooms). Add ensurePairedWorkspaceProvisioned(): defaults canonical work_dir to groups/<folder>, guarantees it is a standalone git repo with an initial commit (detected via a LOCAL .git so we never walk up into the EJClaw checkout and create a stray worktree), and upserts the paired_projects row. Wire it into setup/register.ts so every newly registered room is immediately usable by the full owner→reviewer→arbiter flow. Adds a regression test.
This commit is contained in:
@@ -3,11 +3,12 @@ import crypto from 'crypto';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { DATA_DIR } from './config.js';
|
||||
import { DATA_DIR, GROUPS_DIR } from './config.js';
|
||||
import {
|
||||
getPairedProject,
|
||||
getPairedTaskById,
|
||||
getPairedWorkspace,
|
||||
upsertPairedProject,
|
||||
upsertPairedWorkspace,
|
||||
} from './db.js';
|
||||
import { resolvePairedTaskWorkspacePath } from './group-folder.js';
|
||||
@@ -610,6 +611,62 @@ function getTaskAndProject(taskId: string): {
|
||||
return { task, canonicalWorkDir: project.canonical_work_dir };
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure a room's canonical paired workspace exists so the tribunal flow can
|
||||
* provision an owner worktree and run the reviewer/arbiter.
|
||||
*
|
||||
* Root cause this guards against: a room registered as tribunal with no
|
||||
* `work_dir` → `ensurePairedProject` returns null → no paired task is created →
|
||||
* reviewer/arbiter never run (the symptom seen in web-vstock and other rooms).
|
||||
*
|
||||
* This defaults the canonical work dir to `groups/<folder>`, guarantees it is a
|
||||
* standalone git repo with at least one commit (so `git worktree add` can
|
||||
* resolve HEAD during owner-workspace provisioning), and upserts the
|
||||
* `paired_projects` row. Idempotent — safe to call on every registration.
|
||||
*/
|
||||
export function ensurePairedWorkspaceProvisioned(args: {
|
||||
chatJid: string;
|
||||
groupFolder: string;
|
||||
workDir?: string | null;
|
||||
}): string {
|
||||
const canonicalWorkDir =
|
||||
args.workDir && args.workDir.trim().length > 0
|
||||
? args.workDir
|
||||
: path.join(GROUPS_DIR, args.groupFolder);
|
||||
|
||||
fs.mkdirSync(canonicalWorkDir, { recursive: true });
|
||||
|
||||
// Detect a standalone repo by its OWN `.git` only. A bare `git rev-parse`
|
||||
// would walk up to a parent repo (e.g. the EJClaw checkout that contains
|
||||
// groups/<folder>) and falsely report the dir as a repo — the exact bug that
|
||||
// produced stray EJClaw worktrees. So init a fresh repo when no local .git.
|
||||
if (!fs.existsSync(path.join(canonicalWorkDir, '.git'))) {
|
||||
runGit(['init', '-b', 'main'], canonicalWorkDir);
|
||||
}
|
||||
|
||||
if (!resolveCommit(canonicalWorkDir, 'HEAD')) {
|
||||
// Fresh repo with no commits — owner-workspace provisioning needs a
|
||||
// resolvable HEAD, so seed an empty initial commit with a local identity.
|
||||
runGit(['config', 'user.email', 'claude-bot@ejclaw'], canonicalWorkDir);
|
||||
runGit(['config', 'user.name', 'EJClaw'], canonicalWorkDir);
|
||||
runGit(
|
||||
['commit', '--allow-empty', '-m', 'chore: initialize paired workspace'],
|
||||
canonicalWorkDir,
|
||||
);
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
upsertPairedProject({
|
||||
chat_jid: args.chatJid,
|
||||
group_folder: args.groupFolder,
|
||||
canonical_work_dir: canonicalWorkDir,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
});
|
||||
|
||||
return canonicalWorkDir;
|
||||
}
|
||||
|
||||
function makeWorkspaceRecord(args: {
|
||||
taskId: string;
|
||||
role: PairedWorkspace['role'];
|
||||
|
||||
Reference in New Issue
Block a user