Skip to content

feat(infra): бот за заявяване на Issue-та (/assign) срещу дублирана работа (#230) - #240

Open
cefothe wants to merge 7 commits into
midt-bg:mainfrom
cefothe:feat/issue-claim-upstream
Open

feat(infra): бот за заявяване на Issue-та (/assign) срещу дублирана работа (#230)#240
cefothe wants to merge 7 commits into
midt-bg:mainfrom
cefothe:feat/issue-claim-upstream

Conversation

@cefothe

@cefothe cefothe commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Какво и защо

Въвежда бота за заявяване на Issue-та, предложен в #230: контрибуторите заявяват задача с коментар /assign/unassign, за да я освободят) — без нужда от достъп до репото. Дневна проверка автоматично освобождава „застояли" заемания. Целта е двама души да не тръгват по един и същ Issue.

Реализацията са два тънки GitHub Actions workflow-а над един тестван модул с чиста логика:

  • .github/workflows/assign-issue.yml — обработва /assign и /unassign. Тъй като GitHub не позволява не-колаборатор да е нативен Assignee, заемането се пази като маркер в тялото на Issue-то (<!-- sigma-claim --> + видим ред 🔧 Claimed by: @user) плюс етикет status: in-progress. Същият модел като Rust @rustbot claim.
  • .github/workflows/stale-assignment-check.yml — дневно: подсеща при 14 дни без активност, освобождава при 21; пропуска Issue-та със свързан още отворен PR.
  • scripts/issue-claim.mjs — цялата логика (парсване на командите, четене/писане/чистене на маркера, изчисляване на неактивност, разпознаване на свързан PR, решения за подсещане), покрита с 88 node:test теста (scripts/issue-claim.test.mjs).

Спрямо черновата в #230 тук е втвърдено: недовереното тяло на коментара минава през env (без интерполация в скрипта); защитен JSON.parse (повреден маркер не чупи дневната проверка); глобален strip срещу подправяне на маркера; валидация на потребителското име; невидими <!-- sigma-nudge --> / <!-- sigma-release --> маркери за идемпотентни повторни минавания; retry при 409/422 при запис на тялото; разпознаване на fork PR; timeout-minutes + concurrency; SHA-пинати actions; least-privilege права per job; /unassign на чужд claim само от maintainer (проверка на реалното ниво на достъп, не на author_association).

Свързан issue

Част от #230 (status: needs-decision). Моделът все още чака решение от поддръжниците — този PR е реализацията, ако бъде приет. Пипа .github/, което се застъпва с CI/branch-protection нишката: нужна е координация с @cefothe. Отворените въпроси в #230 (отделен workflow за застояли PR-и; различни 14/21 прозорци по priority; кога си струва Triage роля) остават извън обхвата на този PR.

Вид промяна

  • feat — нова функционалност (+ ci за workflow-ите, docs за плана и CONTRIBUTING)

Как е тествано

  • node --test scripts/issue-claim.test.mjs88/88 минават.
  • pnpm lint (prettier --check) — чисто; pnpm check:docs — минава (планът е индексиран в docs/README.md).
  • End-to-end във форк (cefothe/sigma, живи workflow-и) — покрити сценарии: /assign, /unassign, оспорено заемане (втори потребител), неоторизиран /unassign (не-колаборатор само с read), maintainer override, повторно заемане, празно тяло, подправен маркер, повреден маркер (без грешка), /assign върху PR (пропуска се), race при едновременно заемане (без двойно заемане), case-insensitive команди (/ASSIGN). Дневната проверка е пусната ръчно (workflow_dispatch) и коректно не прави нищо за пресни заемания.

Нужни етикети (действие за поддръжника)

Трябва да се създаде един нов етикет преди/при merge:

  • status: in-progress — в стила на съществуващите status: етикети. Защо: и двата workflow-а зависят от него — assign-issue.yml го слага при заемане, а stale-assignment-check.yml филтрира списъка с Issue-та по него. Ако не съществува, addLabels ще го създаде автоматично с произволен цвят и без описание — затова е по-добре да се създаде предварително:

    gh label create "status: in-progress" --repo midt-bg/sigma \
      --description "По задачата се работи (заявена чрез /assign)" --color 1d76db

Останалите използвани етикети вече съществуват (infra, status: needs-decision). Настройки на Actions: не е нужно да се пипа общото „Read and write" право — per-job permissions: issues: write е достатъчно (проверено на живо).

Чеклист

  • Комитите следват conventional commits и нямат Co-Authored-By: trailer
  • PR-ът е с един логически обхват и е от форк към midt-bg/sigma:main
  • pnpm typecheck — не засяга типове (само .mjs / .yml); ще се провери в CI
  • pnpm test — минава (unit тестовете чрез node --test)
  • pnpm lint е чисто
  • Няма комитнати тайни, .env* или .dev.vars
  • Документацията в docs/ е обновена (план + индекс + CONTRIBUTING)

cefothe added 6 commits July 14, 2026 17:09
Pure, dependency-free logic for the /assign issue-claiming bot (midt-bg#230):
command parsing, claim marker read/write/strip, idle-day computation,
linked-PR awareness, and nudge decisioning. All parse paths guarded
(malformed marker never throws); strip is global (anti-spoof). 84 tests
under node --test, auto-run by scripts-test.yml.
assign-issue.yml: /assign and /unassign comment commands record a claim as a
body marker + status: in-progress label (GitHub refuses to assign
non-collaborators; @rustbot precedent). stale-assignment-check.yml: daily sweep
nudges at 14 days idle and auto-releases at 21, skipping issues with a still-open
linked PR. Both are thin github-script glue over scripts/issue-claim.mjs; actions
SHA-pinned, least-privilege permissions, timeout + concurrency set. Refs midt-bg#230.
Link docs/implementation-plans/230-issue-claim-bot.md from docs/README.md so
the docs-integrity gate (check:docs) passes — every doc must be reachable from
the index.
The workflow if: gate uses GitHub Actions startsWith, which is case-insensitive,
so /ASSIGN runs the job — but parseCommand matched case-sensitively and returned
null, silently no-opping. Lowercase the token so /ASSIGN, /Assign, etc. take
effect. The exact-word guard still rejects /assignee, /assign-me (even uppercased).

@ydimitrof ydimitrof left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Преглед на PR #230 — бот за заявяване на Issue-та (/assign) срещу дублирана работа

Какво прави PR-ът

Добавя GitHub Actions работен поток и помощен скрипт (scripts/issue-claim.mjs), които позволяват на контрибутор да заяви Issue чрез команда /assign в коментар. Заявката се записва като маркер в тялото на Issue-то, а дневен sweep напомня (nudge) и по-късно освобождава изоставени заявки. Целта е да се избегне дублирана работа, без да се блокират други хора, когато заявителят замълчи. Пакетът включва и обстоен тестов файл (scripts/issue-claim.test.mjs, ~50 случая).

Обща оценка

Кодът е с високо качество и е добре закален срещу типичните рискове на issue_comment работни потоци. Фаза-0 сканирането за сигурност и в двете партиди е ЧИСТО: няма твърдо кодирани тайни, няма злонамерени шаблони или нови зависимости, ненадежденото github.event.comment.body се подава през env и се чете от process.env (без израз-инжекция), действията са пинати към SHA, правата са least-privilege по job, sweep-ът е устойчив на едно „отровено“ Issue, а bot-author guard предпазва от feedback-loop.

Най-важна забележка (средна — целостта на данните)

computeIdleDays нулира часовника за неактивност при всяко човешко събитие в timeline-а, а не само при активност на самия заявител. Ако заявителят е замълчал, но други хора коментират/лейбълват Issue-то, то никога няма да бъде авто-освободено — което противоречи на заявената цел на функцията. Препоръка: измервай неактивността спрямо събитията на claim.user.

Други наблюдения (ниски / за потвърждение)

  • Spoof на самоличност (ниско): маркерът за заявка живее в редактируемото тяло на Issue-то, така че може да бъде предварително поставен/подправен. Планът предвижда tamper-proof claim-of-record в коментар от бота (D2) — приемливо за ниско-рискова функция, но заслужава followup.
  • parseClaimMarker чете само първия маркер: по-рано поставен фалшив маркер може да засенчи истинския. stripClaim маха всички (глобален regex), така че sticky-claim е предотвратен, но „> 1 маркер = невалиден“ би затворило и този ъгъл. Да се потвърди, че изходният код не се доверява на маркера за авторизация на /unassign.
  • hasOpenLinkedPr при PR от форк (O3): всеки отворен cross-referenced PR (вкл. от чужд форк) връща true — потенциален вектор за „заключване“ на Issue от външен потребител. Струва си потвърждение спрямо изходния код.
  • Анти-spoof пропуск за shouldNudge: липсва тест, че коментар от човек с вмъкнат <!-- sigma-nudge --> НЕ потиска напомнянето (иначе е възможно вечно избягване на авто-освобождаване).
  • parseCommand покритие: липсват тестове за команда, предшествана от текст/цитат (напр. > /assign), която трябва да върне null.
  • UX несъответствие (незначително): if: гейтът използва startsWith(comment.body, '/assign'), докато parseCommand прави .trim() — коментар с водещи интервали няма да задейства job-а, а /assignee//assign-me пускат job без ефект (хабене на runner).

Тестове

Тестовият пакет е силен и възпроизводим (фиксиран NOW вместо Date.now()): покрива граничните случаи 14/21 дни, null-safety, устойчивост към malformed JSON (S2), анти-spoof при дублирани маркери (S3), валидация на потребителско име (S5) и нулиране от бот (O1). Няма cheater-тестове, TODO-та или мъртъв код. Наблюденията по-горе са предимно въпроси за покритие/дизайн.

Вердикт

VERDICT: COMMENT — няма блокиращи проблеми в сигурността в нито една партида. Преди финално одобрение / merge:

  1. решение по средната забележка за computeIdleDays;
  2. потвърждение на 4-те наблюдения за покритие/дизайн спрямо изходния код;
  3. решение на maintainer-ите по модела needs-decision за #230не мърджвай преди това решение.

Comment thread scripts/issue-claim.mjs Outdated
const events = timeline ?? [];
let lastHumanMs = NaN;
for (const e of events) {
if (e?.actor?.type === 'Bot') continue;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Средна — цялост на данните. computeIdleDays изключва само Bot-събитията, така че часовникът за неактивност се нулира при всяко човешко събитие в timeline-а — включително коментари/лейбъли от хора, различни от заявителя. Ако заявителят (claim.user) е замълчал, но други коментират или триажират issue-то, idle остава малко и задачата никога не се освобождава — което директно противоречи на заявената цел на sweep-а („contributors who picked up an issue and went quiet don't block others").

Препоръка: измервай неактивността спрямо събитията на самия заявител, напр. подай claimedUser и брой само събития, при които e.actor.login === claimedUser (с fallback към createdAt). Ако това е съзнателен избор, добави коментар/тест, който го фиксира като поведение.

Comment thread scripts/issue-claim.mjs Outdated
if (!body) return null;
// Reset lastIndex before exec (MARKER_RE is global; reset avoids stale state).
MARKER_RE.lastIndex = 0;
const match = MARKER_RE.exec(body);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ниско. parseClaimMarker чете само ПЪРВИЯ маркер (MARKER_RE.exec). Тъй като тялото на issue-то е редактируемо от автора му, по-рано поставен фалшив <!-- sigma-claim: ... --> може да засенчи легитимния. stripClaim маха всички маркери (глобален regex, S3), така че sticky-claim е предотвратен, но идентификацията чете първия. Обмисли нормализация „> 1 маркер → третирай като невалиден/без claim", за да затвориш и този griefing вектор (свързано с D2 — claim-of-record в коментар от бота).

if: >-
github.event.issue.pull_request == null &&
github.event.comment.user.type != 'Bot' &&
(startsWith(github.event.comment.body, '/assign') ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Незначително. Гейтът startsWith(github.event.comment.body, '/assign') работи върху суровото тяло, докато parseCommand прави body.trim(). Коментар с водещи интервали (напр. /assign) няма да задейства job-а изобщо, въпреки че parseCommand би го приел — лек UX разнобой. Освен това startsWith пуска job и за /assignee, /assign-me (после безобиден no-op през exact-word guard-а), т.е. се хаби runner. Не е блокиращо; помисли за подравняване на условието (trim/regex) с parseCommand.

);
});

it('human comment containing the words "auto-release" does NOT suppress nudge — O1', () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Анти-spoof пропуск в покритието на shouldNudge. Тук се проверява, че свободен текст „auto-release“ не потиска напомнянето, а тестът на ред ~436 проверява потискане само от коментар на бот, съдържащ NUDGE_MARKER. Липсва обаче тестът за обратния анти-spoof случай: коментар от човек (actor.type === 'User'), който съдържа вмъкнат <!-- sigma-nudge -->, НЕ бива да потиска напомнянето. Ако изходният shouldNudge прави само substring-съвпадение по тялото, без да проверява типа на автора, всеки потребител може да постави невидимия маркер и да блокира напомнянията/авто-освобождаването завинаги. Предлагам да се добави тест, който затвърждава, че само маркер от бот потиска (аналогично на S3 анти-spoof тестовете за claim маркерите).

Comment thread scripts/issue-claim.test.mjs Outdated
assert.equal(hasOpenLinkedPr(timeline), false);
});

it('returns true for a fork-source PR that is still open — O3', () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Този тест затвърждава като „правилно“ поведение, че hasOpenLinkedPr връща true за всеки отворен cross-referenced PR, включително от чужд форк (fork-user/sigma). Ако при откриване на „свързан отворен PR“ stale-sweep-ът пропуска авто-освобождаването, това е потенциален вектор за отказ/заключване: външен потребител може да отвори PR от своя форк, който реферира едно idle Issue, и така да държи заявката (claim) заключена безсрочно. Струва си да се потвърди спрямо изходния код и, ако е нужно, да се стесни условието (напр. само PR-и от базовото репо или авторът да съвпада със заявителя). Ако поведението е умишлено — добавете тест/коментар, който явно документира решението за форк-PR-ите.

});

it('returns /assign when followed by trailing text', () => {
assert.equal(parseCommand('/assign please'), '/assign');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Покритието на parseCommand обхваща водещо празно пространство и завършващ текст, но липсва негативен тест за команда, предшествана от текст или цитат — напр. parseCommand('yes /assign') или quoted reply parseCommand('> /assign') трябва да върнат null. GitHub често цитира предишни коментари с префикс >, така че без такъв тест не е гарантирано, че цитиран/вграден /assign няма да задейства неволна заявка. Предлагам да се добавят 1–2 такива case-а.

Comment thread scripts/issue-claim.test.mjs Outdated
assert.equal(parseClaimMarker(body), null);
});

it('uses first marker when duplicates are present', () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Семантиката „използва първия маркер при дубликати“ е тествана, но си струва да се потвърди спрямо изходния код, че parseClaimMarker не се използва като единствен източник на истина за авторизация на /unassign. Тялото на Issue е редактируемо от неговия автор — ако той вмъкне <!-- sigma-claim: {"user":"жертва"} --> преди легитимния маркер, canUnassign би могъл да бъде подведен. Ако решението за авторизация се взима от labels/assignees, а не от маркера в тялото — идеално; предлагам кратък тест/коментар, който да затвърди това.

…review)

Addresses ydimitrof's review:
- computeIdleDays now measures inactivity from the CLAIMER's own events, not any
  human's, so others commenting can't keep an abandoned claim alive (MEDIUM).
- hasOpenLinkedPr counts only the claimer's own open PR, so a stranger's fork PR
  referencing an idle issue can't lock the claim (griefing guard).
- shouldNudge only treats a BOT-authored nudge marker as a prior nudge, so a human
  can't plant the marker to suppress nudges.
- parseClaimMarker treats >1 marker as no valid claim (planted-marker fail-safe).
Adds tests for each, plus quoted/prefixed parseCommand negatives.
@cefothe

cefothe commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Благодаря за подробния преглед, @ydimitrof! 🙏 Всички забележки са адресирани в ff51dff. По точки:

Средна — computeIdleDays (цялост на данните). ✅ Поправено. Вече мери неактивност спрямо събитията на самия заявител (claimedUser), а не на всеки човек — коментари/триаж от други не поддържат изоставена заявка жива. Fallback към createdAt, ако заявителят няма събития; sweep-ът подава claim.user. Добавени тестове: не-заявител не нулира часовника; собствена активност го нулира; fallback.

Ниско — hasOpenLinkedPr при форк-PR (O3 / griefing). ✅ Стеснено. Само собствен отворен PR на заявителя държи заявката жива (source.issue.user.login === claimedUser). Чужд PR от форк, рефериращ idle Issue, вече не заключва — а нормалният drive-by поток (работа от собствен форк) продължава да работи. Тестове: PR на заявителя (вкл. форк) → true; PR на непознат → false.

Ниско — shouldNudge анти-spoof. ✅ Поправено (беше реален пропуск, не само тест). Само бот-коментар с <!-- sigma-nudge --> се брои за вече-изпратено напомняне; човек не може да потисне напомнянията, вмъквайки маркера. (Авто-освобождаването на 21-ия ден и без това е независимо от напомнянето — но пропускът е затворен.) Добавен тест с човек-автор.

Ниско — parseClaimMarker чете първия маркер. ✅ Втвърдено: вече > 1 маркер → невалидна заявка (fail-safe), така че планиран втори маркер не може да засенчи легитимния. Заедно с глобалния stripClaim (S3) това затваря вектора. Тестът за дубликати вече очаква null.

Покритие — parseCommand цитиран/предшестван /assign. ✅ Добавени тестове: yes /assign и quoted > /assign (GitHub цитира с >) → null. Поведението вече беше коректно; сега е фиксирано с тест.

Незначително — if: гейт vs parseCommand. Прието като съзнателен компромис. GitHub Actions if: няма regex/trim; startsWith е най-евтиният предварителен гейт, а parseCommand е авторитетният филтър (вкл. case-insensitive от 0413947). Воден интервал ( /assign) не задейства job (пренебрежимо), а /assignee пуска job, който излиза веднага без ефект (~секунди runner). Прецених, че не си струва да усложнявам условието.

Авторизация на /unassign спрямо маркера. Маркерът е claim-of-record по дизайн — GitHub не позволява не-колаборатор като нативен Assignee, затова заемането живее в тялото. Подправяне от автора на Issue-то е документираният нисък-риск S1, смекчен от: валидация на потребителското име (S5), новия fail-safe при дубликати и maintainer override (/unassign от write/admin, чрез проверка на реалното ниво на достъп, не на author_association). Tamper-proof claim-of-record в коментар от бота остава планираният followup (D2).

Статус: 96/96 теста минават (node --test), pnpm lint и pnpm check:docs са чисти. Merge остава изчакващ решението на поддръжниците по #230.

@ydimitrof ydimitrof left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API Error: Connection closed mid-response. The response above may be incomplete.

Comment thread scripts/issue-claim.mjs
// Only a BOT-authored comment carrying the marker counts as a prior nudge. Checking the author
// type stops a human from planting <!-- sigma-nudge --> in a comment to suppress nudges forever
// (anti-spoof, sibling to the claim-marker hardening).
const alreadyNudged = events.some(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Среден] Nudge-детекцията е глобална върху цялата timeline → счупен nudge при пре-заявяване.

alreadyNudged търси какъвто и да е bot коментар с nudgeMarker в цялата timeline. Проблем при повторно заявяване: след пълен цикъл заявяване→nudge→release, старият nudge коментар остава завинаги в timeline-а. Когато нов contributor заяви същото issue и стои неактивен ≥14 дни, тази проверка вижда стария <!-- sigma-nudge -->, връща false, и новият заявител бива освободен на 21-я ден без нито едно предупреждение.

O1 реши false-positive-а от споделена проза, но не и този случай. Предложение: ограничете търсенето до събития след последното sigma-release (или след последната claim активност на текущия заявител), за да е nudge-ът относителен спрямо текущото заемане, а не спрямо цялата история на issue-то. Струва си и изричен тестов случай за re-claim в партида 2.


// ── parseCommand ───────────────────────────────────────────────────────────────

describe('parseCommand', () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Покритието на parseCommand е за команди в един ред (/assign please, /assign), но липсва тест за многоредово тяло на коментар. GitHub коментарите често са многоредови, напр. /assign\n\nЩе го поема. Предлагам да добавите тест, който фиксира поведението, когато /assign е първият токен на първия ред, а тялото продължава на следващи редове — за да е ясно дали командата се разпознава спрямо първия ред или спрямо цялото тяло.

});

it('returns null when user contains invalid characters — S5', () => {
const body = '<!-- sigma-claim: {"user":"bad user!"} -->';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Има отличен тест за премахване на ДУБЛИРАНИ банери/маркери, но липсва обратната защита: stripClaim НЕ трябва да премахва легитимен потребителски текст, който случайно съдържа реда **🔧 Claimed by:** или емоджито 🔧. Ако логиката е базирана на широк regex, това е реален риск от загуба на съдържание. Предлагам защитен тест, напр. stripClaim('Виж 🔧 в кода\n**🔧 Claimed by:** пример') да запазва потребителския ред.

@lyubomir-bozhinov lyubomir-bozhinov added the infra Област: infra label Jul 15, 2026

@lyubomir-bozhinov lyubomir-bozhinov left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Прегледах стриктно — одобрявам на връх ff51dff. Ботът е solid и адресира точно рисковете на този клас automation.

  • Actions injection: чисто. comment.body минава като env var (COMMENT_BODY), не се интерполира в script: блок — правилният безопасен модел (интерполация в run:/script: = RCE от недоверен коментар).
  • Exact-command parse. parseCommand взима първия токен и сравнява === '/assign' (case-insensitive), тъй че /assignee, /assign-me, /assigned не задействат заявка. Coarse startsWith gate-ът в YAML-а е само trigger; скриптът пре-парсва точно.
  • Idle от активността на самия claimer (computeIdleDays, e.actor.login === claimedUser), не от updated_at — чужд коментар не нулира таймера. hasOpenLinkedPr пази от release при отворен свързан PR; нъджовете са идемпотентни (маркер).
  • Permissions минимални (issues: write, contents: read); per-issue concurrency; изключване на bot-коментари. 777-редовият тест покрива логиката реално — no cheater tests.

FYI (не блокира): status: in-progress ще се auto-create-не с default цвят при първото addLabels — може да го пре-стилизирате с цвят/описание.

Implements #230. Одобрено; blocked на required CI.

@nedda76 nedda76 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Прегледах workflow-ите и glue-логиката. Добре обмислена, сигурностно-съзнателна реализация на #230.

Червеното CI е несвързано — пребазиране го оправя
Падналата проверка check е таймаут на тест в packages/db/src/refresh-slice.test.ts (sqlite-cli интеграционната сюита, „Test timed out in 5000ms"), а не в нищо, което този PR пипа (само .github/workflows/*, scripts/issue-claim.mjs, docs). Точно това вече е поправено на main в bf8b956 (test(db): raise vitest timeout for the sqlite-cli integration suite, #243). Пребазиране върху актуалния main би вдигнало таймаута и CI-то би минало зелено. Тоест не е нужна промяна по кода тук.

Сигурност — потвърждава се

  • Инжекционната защита е направена правилно: недоверемото тяло на коментара идва през env: COMMENT_BODY и се чете с process.env, никога не се интерполира в стринга на github-script (класическият GitHub Actions script-injection вектор). parseCommand прави авторитетния разбор.
  • issue_comment (не pull_request_target), права стеснени до issues: write, contents: read, без тайни отвъд GITHUB_TOKEN.
  • Action-ите са пиннати по commit SHA (checkout/setup-node/github-script) — supply-chain хигиена.
  • Guard срещу ботове (feedback loop), concurrency група на Issue, retry на 409/422 с пре-четене на тялото. @${actor} е GitHub username → безопасно в markdown.

Бележка — това е продуктово решение, не само код
Самата механика на заемане беше предложена в #230, който още стои с status: needs-decision. Преди мърдж струва да се вземе решение по механизма (маркер в тялото на Issue-то + етикет status: in-progress, огледало на @rustbot claim), защото това е нова постоянна конвенция за контрибуторите (CONTRIBUTING.md се променя).

Кодът е издържан. Гейтовете са два: (1) пребазиране за зелено CI, (2) решение по #230 за самата механика. Само преглед — самият мърдж не е мой.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

infra Област: infra

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants