mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-09-10 19:26:00 +00:00
AgentGit is a thin, DID-gated source-collaboration layer over a backend forge (default Forgejo at git.profullstack.com, BBS-members-only) — not a new git host. M1 implements the contract and engines: - forge/adapter.ts: ForgeAdapter interface (only forge-specific surface) - forge/forgejo.ts: ForgejoAdapter over Forgejo/Gitea REST v1 (injectable fetch, typed errors), incl. ensureUser for member provisioning - access.ts: gateAccess DID membership gate (owner/role/visibility) - merge-policy.ts: evaluateMergePolicy pure engine (reviews, reputation floor, checks, escrow, merge method, agent-merge toggle) - service.ts: AgentGitService ties gate + policy to the adapter; refuses policy-failing merges; provisionMember hook for AgentBBS - schemas: logicsrc-repo + logicsrc-pull-request, registered in @logicsrc/validators with fixtures - docs/agentgit.md spec; plugin wired into root build (default/disabled) 27 vitest tests pass; full monorepo build green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
88 lines
3.6 KiB
TypeScript
88 lines
3.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { DEFAULT_MERGE_POLICY, type MergePolicy, type PullRequest } from "./domain.js";
|
|
import { evaluateMergePolicy } from "./merge-policy.js";
|
|
|
|
const basePr: Pick<PullRequest, "status" | "reviews" | "checks"> = {
|
|
status: "open",
|
|
reviews: [{ reviewer_did: "carol.example", decision: "approve", reputation: 10 }],
|
|
checks: [{ name: "ci", status: "passing" }]
|
|
};
|
|
|
|
const human = { did: "alice.example", isAgent: false };
|
|
const agent = { did: "bot.example", isAgent: true };
|
|
|
|
describe("evaluateMergePolicy", () => {
|
|
it("passes a clean PR with the default policy", () => {
|
|
const result = evaluateMergePolicy({ policy: DEFAULT_MERGE_POLICY, pr: basePr, method: "squash", actor: human });
|
|
expect(result).toEqual({ satisfied: true, reasons: [] });
|
|
});
|
|
|
|
it("blocks when there are not enough approvals", () => {
|
|
const result = evaluateMergePolicy({
|
|
policy: DEFAULT_MERGE_POLICY,
|
|
pr: { ...basePr, reviews: [] },
|
|
method: "squash",
|
|
actor: human
|
|
});
|
|
expect(result.satisfied).toBe(false);
|
|
expect(result.reasons.join(" ")).toMatch(/approving review/);
|
|
});
|
|
|
|
it("ignores approvals below the reputation floor", () => {
|
|
const policy: MergePolicy = { ...DEFAULT_MERGE_POLICY, reviewer_reputation_min: 50 };
|
|
const result = evaluateMergePolicy({ policy, pr: basePr, method: "squash", actor: human });
|
|
expect(result.satisfied).toBe(false);
|
|
expect(result.reasons.join(" ")).toMatch(/reputation >= 50/);
|
|
});
|
|
|
|
it("blocks when changes are requested", () => {
|
|
const result = evaluateMergePolicy({
|
|
policy: DEFAULT_MERGE_POLICY,
|
|
pr: { ...basePr, reviews: [{ reviewer_did: "carol.example", decision: "request_changes" }] },
|
|
method: "squash",
|
|
actor: human
|
|
});
|
|
expect(result.satisfied).toBe(false);
|
|
expect(result.reasons).toContain("changes requested by a reviewer");
|
|
});
|
|
|
|
it("blocks on non-passing checks when required", () => {
|
|
const result = evaluateMergePolicy({
|
|
policy: DEFAULT_MERGE_POLICY,
|
|
pr: { ...basePr, checks: [{ name: "ci", status: "failing" }] },
|
|
method: "squash",
|
|
actor: human
|
|
});
|
|
expect(result.satisfied).toBe(false);
|
|
expect(result.reasons.join(" ")).toMatch(/checks not passing/);
|
|
});
|
|
|
|
it("requires a funded escrow when escrow_required", () => {
|
|
const policy: MergePolicy = { ...DEFAULT_MERGE_POLICY, escrow_required: true };
|
|
expect(evaluateMergePolicy({ policy, pr: basePr, method: "squash", actor: human }).satisfied).toBe(false);
|
|
expect(evaluateMergePolicy({ policy, pr: basePr, method: "squash", actor: human, escrowFunded: true }).satisfied).toBe(true);
|
|
});
|
|
|
|
it("rejects disallowed merge methods", () => {
|
|
const result = evaluateMergePolicy({ policy: DEFAULT_MERGE_POLICY, pr: basePr, method: "merge", actor: human });
|
|
expect(result.satisfied).toBe(false);
|
|
expect(result.reasons.join(" ")).toMatch(/not allowed/);
|
|
});
|
|
|
|
it("blocks agent merges when allow_agent_merge is false", () => {
|
|
const policy: MergePolicy = { ...DEFAULT_MERGE_POLICY, allow_agent_merge: false };
|
|
expect(evaluateMergePolicy({ policy, pr: basePr, method: "squash", actor: agent }).satisfied).toBe(false);
|
|
expect(evaluateMergePolicy({ policy, pr: basePr, method: "squash", actor: human }).satisfied).toBe(true);
|
|
});
|
|
|
|
it("never satisfies an already merged PR", () => {
|
|
const result = evaluateMergePolicy({
|
|
policy: DEFAULT_MERGE_POLICY,
|
|
pr: { ...basePr, status: "merged" },
|
|
method: "squash",
|
|
actor: human
|
|
});
|
|
expect(result.satisfied).toBe(false);
|
|
expect(result.reasons).toContain("pull request is merged");
|
|
});
|
|
});
|