feat(discord): git verbs for the Discord Sage on the shared-signals root, seat identity through a package credential helper, vault record protocol (#1509)

Row 24. A writable root that is a git work tree may carry a git object in
the binding; the seat then has git_status, git_commit (explicit paths, seat
author, Requested-by trailer from the envelope requester, push at once per
D6), git_pull (ff-only) and git_push (one branch, never force), plus
reserve_id and per-write clone locks under protocol vault. Git children run
with no host config and one credential helper, bin/git-credential.mjs,
reading the 0600 seat token file named in the binding; the fleet helper
serves only the Gitea hosts. Suite 58/58, node 143. rev-code-02 APPROVED
round 1 (#1509 comment 26375, tree 82ab962f).

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
2026-09-18 07:52:35 -05:00
co-authored by Claude Fable 5.1
parent 1685deb423
commit 1949ed8d31
23 changed files with 1284 additions and 48 deletions
+13 -3
View File
@@ -24,6 +24,7 @@ import { isAbsolute, join, resolve, sep } from "node:path";
import { homedir } from "node:os";
import { DiscordError } from "./errors.mjs";
import { TOOL_DEFAULTS } from "./tools.mjs";
import { loadGitConfig } from "./git.mjs";
export const BINDING_VERSION = 1;
export const BINDING_NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/;
@@ -45,7 +46,7 @@ const ENGINE_KEYS = ["provider", "model", "thinking"];
const LIMIT_KEYS = Object.keys(LIMIT_DEFAULTS);
const CONTEXT_KEYS = ["files"];
const TOOLS_KEYS = ["roots", "maxFileBytes", "maxCallsPerTurn", "web"];
const ROOT_KEYS = ["name", "path", "write"];
const ROOT_KEYS = ["name", "path", "write", "git"];
const ROOT_NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/;
export function defaultConfigPath(env = process.env) {
@@ -197,7 +198,16 @@ export function validateBinding(raw, where = "binding") {
if (rpath.split(sep).some((seg) => seg.startsWith(".") && seg.length > 0)) throw new DiscordError(`${w}: path must not have a dot-prefixed segment (${rpath})`);
if (resolve(rpath) === sep || resolve(rpath) === homedir()) throw new DiscordError(`${w}: path must not be the filesystem root or the home directory`);
if (r.write !== undefined && r.write !== true && r.write !== false) throw new DiscordError(`${w}: write must be true or false`);
return Object.freeze({ name: rname, path: rpath, write: r.write === true });
let git = null;
if (r.git !== undefined) {
if (r.write !== true) throw new DiscordError(`${w}: git needs write: true`);
try {
git = loadGitConfig(r.git, `${w}.git`, null);
} catch (err) {
throw new DiscordError(err.message);
}
}
return Object.freeze({ name: rname, path: rpath, write: r.write === true, git });
});
if (new Set(roots.map((r) => r.name)).size !== roots.length) throw new DiscordError(`${where}.tools: duplicate root name`);
const mergedTools = { ...TOOL_DEFAULTS, ...raw.tools, roots };
@@ -338,7 +348,7 @@ export function resolveToolRoots(binding, { dataRoot }) {
if (!st.isDirectory()) throw new DiscordError(`tool root ${r.name} is not a directory: ${r.path}`);
const real = realpathSync(r.path);
if (real === data || real.startsWith(data + sep) || data.startsWith(real + sep)) throw new DiscordError(`tool root ${r.name} overlaps the data root: ${r.path}`);
return { name: r.name, path: real, write: r.write };
return { name: r.name, path: real, write: r.write, ...(r.git ? { git: { branch: r.git.branch, identity: r.git.identity, tokenFile: r.git.tokenFile, author: `${r.git.author.name} <${r.git.author.email}>`, ...(r.git.protocol ? { protocol: r.git.protocol } : {}) } } : {}) };
});
return { roots, maxFileBytes: binding.tools.maxFileBytes, maxCallsPerTurn: binding.tools.maxCallsPerTurn, ...(binding.tools.web ? { web: { searxng: binding.tools.web.searxng, maxFetchBytes: binding.tools.web.maxFetchBytes } } : {}) };
}