From 6521d9c43199178532c5b2f0ad9c8611ffabaf27 Mon Sep 17 00:00:00 2001 From: Aleksey Shakhmatov Date: Thu, 16 Jul 2026 13:13:35 +0300 Subject: [PATCH] fix(permission-gate): guard release-* branch and v* tag pushes (deploy convention) Deploy convention at M.Video: a release-* branch push deploys to stage and a v* tag push deploys to PROD. The old guard only matched release/* (slash) and never caught tag pushes, so a prod deploy could run unconfirmed. - permission-gate: match main/master, release-[-/], --tags/--follow-tags, and whitespace-preceded v tags; false-positive-safe (space discriminator) - create-mr.sh: refuse source branch release-* (not just release/*) - jira-workflow doc updated to release-* Verified with a behavioral test suite (10 cases incl. v2, false-positive checks). --- extensions/permission-gate.ts | 10 ++++++---- skills/jira-workflow/SKILL.md | 2 +- skills/jira-workflow/scripts/create-mr.sh | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/extensions/permission-gate.ts b/extensions/permission-gate.ts index 9887003..271d3a2 100644 --- a/extensions/permission-gate.ts +++ b/extensions/permission-gate.ts @@ -22,10 +22,12 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; const DANGEROUS_PATTERNS: Array<{ re: RegExp; reason: string }> = [ { re: /\brm\s+(-\w*r\w*f|-\w*f\w*r|--recursive)/i, reason: "recursive force delete (rm -rf)" }, { re: /\bgit\s+push\b[^\n]*(--force\b|--force-with-lease\b|\s-f\b)/i, reason: "force push" }, - { - re: /\bgit\s+push\b[^\n]*\b(main|master|release\/[^\s]+)\b/i, - reason: "push to a protected branch (main/master/release/*)", - }, + { re: /\bgit\s+push\b[^\n]*\b(main|master)\b/i, reason: "push to main/master" }, + // Deploy convention: a `release-*` branch push deploys to STAGE, and a `v*` + // tag push deploys to PROD. Guard both the branch and the tag pushes. + { re: /\bgit\s+push\b[^\n]*\brelease[-/]\S+/i, reason: "push to a release-* branch (deploys to stage)" }, + { re: /\bgit\s+push\b[^\n]*(--tags\b|--follow-tags\b)/i, reason: "push tags (a v* tag deploys to PROD)" }, + { re: /\bgit\s+push\b[^\n]*\sv\d[\w.-]*/i, reason: "push a v* version tag (deploys to PROD)" }, { re: /\bkubectl\b[^\n]*(--context[=\s]?\S*prod|--namespace[=\s]?\S*prod|\bctx-prod\b)/i, reason: "kubectl against a production context/namespace", diff --git a/skills/jira-workflow/SKILL.md b/skills/jira-workflow/SKILL.md index a0c9d87..afe6287 100644 --- a/skills/jira-workflow/SKILL.md +++ b/skills/jira-workflow/SKILL.md @@ -52,7 +52,7 @@ GITLAB_TOKEN=... ./scripts/create-mr.sh "Fix PROJ-123: NPE" main "" PROJ-123 Флаги окружения `create-mr.sh`: `GITLAB_TOKEN` (обязателен), `GITLAB_HOST` (override хоста), `MR_PUSH=0` (не пушить ветку), `MR_DRY_RUN=1` (показать запрос без вызова API). -MR из защищённой ветки (`main`/`master`/`release/*`) скрипт создавать откажется. +MR из защищённой/релизной ветки (`main`/`master`/`release-*`) скрипт создавать откажется. ## Соглашения diff --git a/skills/jira-workflow/scripts/create-mr.sh b/skills/jira-workflow/scripts/create-mr.sh index 613e1c2..992d74b 100755 --- a/skills/jira-workflow/scripts/create-mr.sh +++ b/skills/jira-workflow/scripts/create-mr.sh @@ -26,8 +26,8 @@ JIRA_KEY="${4:-}" # --- Refuse to open an MR *from* a protected branch (safety). ---------------- case "$SOURCE" in - main | master | release/*) - echo "Refusing: source branch '$SOURCE' looks protected. Create a feature branch first." >&2 + main | master | release-* | release/*) + echo "Refusing: source branch '$SOURCE' looks protected/release. Create a feature branch first." >&2 exit 1 ;; esac