Quality gates, documentation, release automation та internal tools

Автоматизація стає небезпечною не тоді, коли падає. Небезпечний момент настає, коли команда покладається на її verdict, але не розуміє evidence та recovery. На прикладі однієї зміни в TeamOrbit пов'яжемо merge gate, docs drift, release proposal, artifact promotion і безпечний повтор частково виконаного release.

Сьогодні пройдемо: рівень 22. CI, PR, tests, artifacts і read-only Claude analysis вважаємо робочими інструментами.

Gate стає залежністю команди

Required check займає 18 хвилин і часом червоніє без зміни code. Через тиждень розробники натискають rerun, не читаючи log. Такий gate уже перевіряє не якість, а терпіння команди.

Сам би не тримав noisy check у required до останнього. Спочатку відновив би довіру до нього, а вже потім дав би право блокувати.

Noisy gate не підвищує якість. Він навчає команду ігнорувати червоний signal.

Ризик визначає evidence та verdict

TeamOrbit додає --timezone до CLI-команди експорту. Код зелений, але operator guidance може не оновитися. Gate починається з цього ризику, а не з вибору action.

gate: cli-doc-contract
risk: public CLI changed but operator guidance stayed stale
evidence: npm run check:docs -- export
mode: observe
owner: sam@teamorbit
failure_artifact: artifacts/cli-doc-contract.diff

Required status працює як API для рішень команди. Його output contract важливіший за назву action.


Перевірка заслуговує на право блокувати

Гарна ідея ще не означає, що check має право зупинити кожен PR. Спочатку він спостерігає за реальними змінами, показує would-block cases і доводить, що команда може відрізнити signal від шуму.

flowchart LR O["Observe"] --> C["Calibrate"] C --> R["Required"] R --> Q["Quarantine"] Q --> C R --> X["Retire"]

Право можна відкликати: noisy check повертається до informational mode, а signal, що дублюється, прибирають із pipeline.

ПеріодWrong blocksMedian
перші 20 PR1 із 448 sec
наступні 20 PR0 із 342 sec

40 PR - дані TeamOrbit, а не стандарт. Після суттєвих змін у checker або самому ризику gate знову проходить етап observe.

Право блокувати потрібно вміти надати й відкликати. Поріг залежить від ціни false block і пропущеного ризику.

Hard gate лише для відтворюваного факту

AI finding корисний як lead: він називає ризик, який ще не оформлений у rule. Але status з'являється лише там, де команда може повторити перевірку без моделі.

Required evidenceAdvisory evidence
tests, lint, type-checkClaude review finding
contract та executable examplearchitecture або docs suspicion
calibrated coverage та basic scanningrelease notes phrasing
artifact integrity та release preflightusability concern

Кілька AI reviewers дають кілька hypotheses, а не кворум. Якщо finding повторюється, його можна відтворити й описати як machine-checkable contract, команда пише deterministic check і калібрує його в observe. Решта залишається advisory або переходить у human review.


Червоний gate повертає наступний крок

Червона позначка без пояснення перекладає підтримку gate на кожного автора PR. Failure output має одразу показати mismatch і команду для відтворення.

FAIL cli-doc-contract
changed: src/commands/export.ts
mismatch: --timezone defaults to UTC; README says local
reproduce: npm run check:docs -- export
owner: sam@teamorbit
artifact: artifacts/cli-doc-contract.diff

Так, це довше за повідомлення quality gate failed. Зате developer не починає розслідування з пошуку власника чужої загадки. І ще: rerun без нового evidence - не recovery.

Gate failure не доводить product failure. Зламаний checker має втратити право блокувати, доки знову не почне давати надійний signal.

Docs gate ловить drift, а не відсутність файлу

Правило code changed, docs must change швидко породжує фіктивні README-edits. Потрібен інший зв'язок: public surface змінився, отже конкретний executable contract має підтвердити documentation.

sequenceDiagram participant D as "Diff" participant C as "Contract check" participant A as "Claude advisory" participant H as "Developer" D->>C: CLI changed C-->>H: Exact mismatch and command D->>A: Bounded diff and generated help A-->>H: Suspected docs impact H->>C: Fix docs and rerun

Claude розширює пошук affected docs, але exact mismatch підтверджує generator, schema diff або executable example. Модель пропонує напрямок, command повертає verdict.


TeamOrbit: code зелений, інструкція бреше

Ось у чому конфлікт. Test suite цього не ловить, бо runtime behavior коректний. Помилку бачить людина, яка повірила README.

$ teamorbit export --help
--timezone <iana>  Default: UTC

$ rg "timezone" README.md
Export uses the operator local timezone by default.

$ npm run check:docs -- export
FAIL default mismatch: CLI=UTC docs=local

CI передає diff і generated help у read-only run: без tools, без session persistence, з лімітом turns. Тут важливий новий output contract, а не повтор CLI flags:

{
  "affected_docs": ["README.md"],
  "evidence": ["CLI default UTC != docs local"],
  "uncertainty": ["docs/cli.md was not part of this run input"]
}

Review тепер обговорює реальний default, а не якість прози.


Генеруй reference, пиши reasoning

Не весь текст варто синхронізувати вручну. Машина добре синхронізує факти, які вже є в code; причини й обмеження все одно належать автору рішення.

Тип знанняРобоча практика
CLI flags, OpenAPI, config referenceгенерувати й diff-ити
code sample, install commandвиконувати в CI
migration, trade-off, limitationписати й review-ити вручну
runbookперевіряти команди, owner та assumptions

Claude може підготувати draft за наявним evidence, але не повинен вигадувати rationale або compatibility claim. Найкраща docs automation зменшує обсяг тексту, який команда має підтримувати вручну.


Release починається з proposal

Merge збирає change, але ще не створює release. Спочатку потрібен review surface, де version, user impact і artifact identity видно в одному diff.

Claude редагує draft і позначає uncertain impact, але не визначає SemVer за commit titles. Почати можна без робота: перший release PR цілком можна зібрати вручну.

Git log описує історію розробки. Release notes мають описувати помітну для користувача різницю.

Release просуває перевірений artifact

Release пов'язує три identity: Git commit, package integrity і registry version. Повторний build створює четвертий об'єкт, якого ці checks не бачили.

flowchart LR C["Commit 8f31c2a"] --> B["Build"] B --> A["Package sha512-q1v3...G9w"] A --> T["Verify package"] T --> G{"Gate passed?"} G -->|yes| R["Release candidate"] R --> P["Publish same integrity"] G -->|no| S["Stop"]

Tag вказує на commit 8f31c2a. Manifest пов'язує цей commit із package integrity, а proposal і registry record зберігають mapping. Version без commit та integrity не відповідає на запитання, що саме пройшло checks.

Identity chain ламається на першому ідентифікаторі, який automation не звірила.


Publish має пережити повтор

Registry прийняв @teamorbit/cli@2.8.0, а timeout стався до tag. Rerun не знає, що встигло змінитися, тому спочатку отримує single-writer lock і читає state.

flowchart TD L["Lock package-version"] --> S["Read registry state"] S --> V{"Read result?"} V -->|error| X["Stop: state unknown"] V -->|missing| U["Publish verified package"] V -->|found| D{"Read-back integrity matches?"} U --> D D -->|no| X D -->|yes| T{"Tag target?"} T -->|missing| C["Create tag at manifest commit"] T -->|error| X T -->|other commit| X T -->|manifest commit| N["Create or update notes"] C --> T

Перший run і resume проходять через одну state machine. missing, found та read error - різні стани: після write job система знову читає registry, а після створення tag ще раз перевіряє target commit.

npm public registry не дозволить повторно використати ту саму пару name@version, навіть після unpublish. Integrity mismatch або чужий tag target зупиняють job: automation не переписує зовнішню історію.

Idempotent не означає concurrency-safe: один package@version обслуговує один release run. Роль lock у GitHub Actions відіграє concurrency group: другий запуск чекає в черзі й не скасовує перший.

Не кожен повтор вартий нового інструмента

Після четвертого ручного release audit хочеться одразу зібрати plugin. Але кількість користувачів визначає лише потребу в distribution. Сам механізм обираємо за типом роботи: deterministic operation або Claude reasoning.

Спостережуване завданняМінімальна відповідьПрихована ціна
одноразове й ще не зрозумілене будувати toolмайже нульова
повторювана deterministic operationscript або CLItests та interface
повторюване Claude reasoningskillprompt eval та onboarding
потрібні live external capabilitiesMCPauth та availability
кілька extension assets у різних repoplugincompatibility та support

Це decision matrix, а не maturity ladder. Shared script може обслуговувати всю команду; skill потрібен не через кількість людей, а тоді, коли model judgment стає частиною workflow. У TeamOrbit досить scripts/release-audit.mjs.

Якщо не можна назвати saved step і майбутнього owner, поки дешевше не будувати.

Другий користувач перетворює script на team asset

Merge показує, що script існує. Другий користувач показує, що ним можна користуватися без автора поруч. Це і є перший acceptance test внутрішнього інструмента.

Для repo-local script version і support boundary можуть успадковуватися від repository. Окремі versioning, telemetry та compatibility policy з'являються, коли є зовнішні consumers або великий blast radius.


Recovery обирає дію за станом

Rollback підходить не для кожного side effect. Спочатку читаємо external state і порівнюємо з manifest, потім обираємо recovery.

flowchart TD F["Automation red"] --> E{"External side effect possible?"} E -->|no| K{"Cause known?"} K -->|yes| R["Targeted rerun"] K -->|no| I["Stop and investigate"] E -->|yes| Q["Read current state"] Q --> M{"State matches manifest?"} M -->|yes| C["Resume missing step"] M -->|no| B["Stop and choose recovery"] I --> O["Owner and follow up"] B --> O

Звичайний функціональний npm-баг: конкретну версію позначають deprecated і випускають patch.

Якщо йдеться про secret, malware або private data, потрібен інший path: stop, revoke credentials і рішення про unpublish за incident procedure, за потреби з registry support.

Незадокументований bypass створює паралельний release path. Команда швидко починає довіряти йому більше, ніж gate.

Фінал: автоматизація заслуговує на authority

Фінальний claim стосується одного change, одного package й одного перевіреного стану release. Усе, що лишилося за межами evidence, названо прямо.

CHANGE
  PR 1842: add --timezone to teamorbit export

GATES
  cli-doc-contract: PASS, required, owner sam@teamorbit
  Claude docs impact: advisory, reviewed

DOCS
  generated help tested; README corrected
  change fragment: changes/1842.added.md

RELEASE
  proposal: 2.8.0 approved
  lock: one run for @teamorbit/cli@2.8.0
  commit: 8f31c2a
  integrity: sha512-q1v3...G9w==
  partial publish: resumed after integrity and tag-target checks

INTERNAL TOOL
  release-audit: informational, second user verified

NOT PROVEN
  production rollout health; production permission policy

Automation отримує authority не тому, що вона автоматична. Gate має бути відтворюваним і owned; release - traceable та retry-safe; internal tool - корисним не лише автору й таким, що його можна вимкнути без розкопок.