GIT4: the seat git's branch answers a branch's head and whether a rule refuses a direct push and admits only merges
Builds: design 47 G §5, §111, §310 branch Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
@@ -170,6 +170,7 @@
|
||||
"scope": "mesh",
|
||||
"serves": [
|
||||
"pull",
|
||||
"branch",
|
||||
"open-pulls",
|
||||
"tags",
|
||||
"trees",
|
||||
@@ -251,7 +252,7 @@
|
||||
"type": "string",
|
||||
"trusted": true,
|
||||
"runtime": true,
|
||||
"default": "{\"publish\":[\"mesh-build-go\",\"mesh-build-node\"],\"publish-part\":[\"mesh-build-go\",\"mesh-build-node\"],\"remove\":[\"mesh-delivery-builder\"],\"versions\":[\"*\"],\"pull\":[\"mesh-delivery-forge-bridge\",\"mesh-delivery-gate\"],\"open-pulls\":[\"mesh-delivery-forge-bridge\",\"mesh-delivery-indexer\",\"mesh-delivery-gate\"],\"tags\":[\"mesh-delivery-forge-bridge\"],\"trees\":[\"mesh-delivery-forge-bridge\",\"mesh-delivery-checker\",\"mesh-delivery-indexer\"],\"file\":[\"mesh-delivery-forge-bridge\",\"mesh-delivery-checker\",\"mesh-delivery-indexer\"],\"contains\":[\"mesh-delivery-indexer\",\"mesh-delivery-gate\"],\"statuses\":[\"mesh-delivery-gate\"],\"status\":[\"mesh-delivery-forge-bridge\"],\"comment\":[\"mesh-delivery-forge-bridge\"],\"view\":[\"mesh-delivery-forge-bridge\"],\"note\":[\"mesh-delivery-forge-bridge\"],\"merge-pull\":[\"mesh-delivery-forge-bridge\"]}",
|
||||
"default": "{\"publish\":[\"mesh-build-go\",\"mesh-build-node\"],\"publish-part\":[\"mesh-build-go\",\"mesh-build-node\"],\"remove\":[\"mesh-delivery-builder\"],\"versions\":[\"*\"],\"pull\":[\"mesh-delivery-forge-bridge\",\"mesh-delivery-gate\"],\"branch\":[\"mesh-delivery-forge-bridge\",\"mesh-delivery-indexer\"],\"open-pulls\":[\"mesh-delivery-forge-bridge\",\"mesh-delivery-indexer\",\"mesh-delivery-gate\"],\"tags\":[\"mesh-delivery-forge-bridge\"],\"trees\":[\"mesh-delivery-forge-bridge\",\"mesh-delivery-checker\",\"mesh-delivery-indexer\"],\"file\":[\"mesh-delivery-forge-bridge\",\"mesh-delivery-checker\",\"mesh-delivery-indexer\"],\"contains\":[\"mesh-delivery-indexer\",\"mesh-delivery-gate\"],\"statuses\":[\"mesh-delivery-gate\"],\"status\":[\"mesh-delivery-forge-bridge\"],\"comment\":[\"mesh-delivery-forge-bridge\"],\"view\":[\"mesh-delivery-forge-bridge\"],\"note\":[\"mesh-delivery-forge-bridge\"],\"merge-pull\":[\"mesh-delivery-forge-bridge\"]}",
|
||||
"description": "per verb of the seats the module holds, the callers that may call it (JSON object of verb to callers; * every proved caller, operator the operator's session); each holder reads the lines of its own verbs. The package seats' lines are design 51's (empty is that list for them); the seat git's are design 47's: its reads the parts that declare them in invokes, its acts (status, comment, view, note, merge-pull) the forge bridge alone, and a verb of git with no line is called by no one"
|
||||
}
|
||||
},
|
||||
|
||||
+15
-2
@@ -20,7 +20,7 @@ import { callerOf, refusal, parseVerbCallers, type VerbCallers } from "@novox/me
|
||||
import { config } from "@novox/mesh-sdk/runtime";
|
||||
import {
|
||||
seat as GIT, verbs as GIT_VERBS,
|
||||
type ContainsAnswer, type CommentAnswer, type ErrorKind, type FileAnswer, type MergePullAnswer, type Merging, type NoteAnswer,
|
||||
type BranchAnswer, type ContainsAnswer, type CommentAnswer, type ErrorKind, type FileAnswer, type MergePullAnswer, type Merging, type NoteAnswer,
|
||||
type OpenPull, type OpenPullsAnswer, type Pull, type PullAnswer, type Status, type StatusAnswer, type StatusesAnswer,
|
||||
type TagsAnswer, type TreesAnswer, type ViewAnswer,
|
||||
} from "@novox/mesh-delivery-contracts-git";
|
||||
@@ -170,6 +170,14 @@ export function seatTools(gitea: GiteaClient, deps: SeatDeps): ToolDefinition[]
|
||||
const pull = pullOf(full, p);
|
||||
return pull.state === "open" ? { pull, merging: await mergingOf(gitea, owner, repo, p) } : { pull };
|
||||
}),
|
||||
verb(GIT_VERBS.branch, "The seat git's branch: a branch's head, and whether a protection rule refuses a direct push and admits only merges.",
|
||||
async (args): Promise<BranchAnswer> => {
|
||||
const { owner, repo, full } = repositoryOf(args.repository);
|
||||
const branch = String(args.branch ?? "");
|
||||
// The head first: a branch the forge does not hold is its 404, the kind not-found, never an unprotected branch.
|
||||
const head = await gitea.branchHead(owner, repo, branch);
|
||||
return { repository: full, branch, head, protected: pushRefused(await gitea.appliedProtection(owner, repo, branch)) };
|
||||
}),
|
||||
verb(GIT_VERBS.openPulls, "The seat git's open-pulls: every open pull request of every repository, with the statuses its base requires and those its head has.",
|
||||
async (): Promise<OpenPullsAnswer> => {
|
||||
const pulls: OpenPull[] = (await gitea.openPulls()).map((o) => ({
|
||||
@@ -273,6 +281,11 @@ export function seatTools(gitea: GiteaClient, deps: SeatDeps): ToolDefinition[]
|
||||
];
|
||||
}
|
||||
|
||||
/** Whether a protection rule refuses a direct push and admits only merges: the rule exists and takes no push. */
|
||||
export function pushRefused(rule: { enable_push?: boolean } | null): boolean {
|
||||
return !!rule && rule.enable_push === false;
|
||||
}
|
||||
|
||||
function holds(comments: GiteaComment[], id: string): boolean {
|
||||
return comments.some((c) => String(c.body ?? "").includes(actMarker(id)));
|
||||
}
|
||||
@@ -295,7 +308,7 @@ export async function mergePull(gitea: GiteaClient, args: Record<string, unknown
|
||||
return no(`its head is ${String(pull.head_sha).slice(0, 12)}, not the ${head.slice(0, 12)} the caller names`);
|
||||
}
|
||||
const rule = await gitea.appliedProtection(owner, repo, String(pull.base ?? ""));
|
||||
if (!rule || rule.enable_push !== false) {
|
||||
if (!pushRefused(rule)) {
|
||||
return no(`its base ${pull.base} is guarded by no protection rule that refuses a push`);
|
||||
}
|
||||
try {
|
||||
|
||||
@@ -562,6 +562,10 @@ test("every read and act of the seat git answers its contract, and an error is a
|
||||
["pull", "by merge commit", { repository: "novox/app", "merge-commit": OTHER }, {}, (a) => [a.pull.state, a.pull["merge-commit"], a.merging], ["merged", OTHER, undefined]],
|
||||
["pull", "unknown", { repository: "novox/app", number: 404 }, {}, (a) => [a.pull, a["error-kind"]], [undefined, "not-found"]],
|
||||
["pull", "forge down", { repository: "novox/app", number: 9 }, { getPullRequest: down }, (a) => [a.pull, a["error-kind"]], [undefined, "failed"]],
|
||||
["branch", "a branch a rule guards", { repository: "novox/app", branch: "main" }, { branchHead: async () => SHA, appliedProtection: async () => ({ rule_name: "main", enable_push: false }) }, (a) => [a.head, a.protected], [SHA, true]],
|
||||
["branch", "a branch no rule guards", { repository: "novox/app", branch: "scratch" }, { branchHead: async () => SHA, appliedProtection: async () => null }, (a) => [a.head, a.protected], [SHA, false]],
|
||||
["branch", "a rule that takes a push", { repository: "novox/app", branch: "main" }, { branchHead: async () => SHA, appliedProtection: async () => ({ rule_name: "main", enable_push: true }) }, (a) => [a.protected], [false]],
|
||||
["branch", "no such branch", { repository: "novox/app", branch: "nope" }, { branchHead: gone }, (a) => [a.head, a["error-kind"]], [undefined, "not-found"]],
|
||||
["open-pulls", "one", {}, {}, (a) => [a.pulls.length, a.pulls[0].required], [1, ["mesh/check"]]],
|
||||
["open-pulls", "forge down", {}, { openPulls: down }, (a) => [a.pulls, a["error-kind"]], [undefined, "failed"]],
|
||||
["tags", "a page", { repository: "novox/app", prefix: "go/" }, {}, (a) => [a.tags, a.page, a.bound, a.more], [["go/v0.1.1", "go/v0.1.0"], 1, 50, undefined]],
|
||||
|
||||
Reference in New Issue
Block a user