From 59c991c15a2a49d6da2e05c7dffbb3051a84a8e0 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasilev Date: Sat, 15 Aug 2026 18:57:40 +0700 Subject: [PATCH 1/2] =?UTF-8?q?fix(ci):=20rewrite=20dev-enforcement=20?= =?UTF-8?q?=E2=80=94=20it=20had=20never=20run=20a=20single=20job?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same five faults as the trinity-fpga copy: the YAML did not parse ('steps:' indented inside 'if:', then 'run:' inside 'if:', so GitHub compiled zero jobs and titled the run by its file path); no job had a checkout, so 'git log' ran outside a repository; $COMMIT was never assigned, so the issue test could only print 'Missing issue ID'; the regex required a literal '[' at the start of the subject; and session-check gated CI on local dev-session state that CI cannot see. Reporting, not blocking, and that differs from the trinity-fpga copy on purpose. Measured on the last 60 non-merge commits of each main: trinity-fpga 3 / 60 ( 5%) would fail trinity 25 / 60 (42%) would fail This repository's practice is largely prose subjects, many in Russian, while CLAUDE.md documents (): . That gap is a policy question, not something to settle by switching a gate to blocking: a check failing 42% of the work is a check people route around. It reports counts and leaves the decision to a reader who can also change the rule. Co-Authored-By: Claude Opus 5 --- .github/workflows/dev-enforcement.yml | 129 ++++++++++++++++++-------- 1 file changed, 88 insertions(+), 41 deletions(-) diff --git a/.github/workflows/dev-enforcement.yml b/.github/workflows/dev-enforcement.yml index 4f0ae6ceaf..531e89485a 100644 --- a/.github/workflows/dev-enforcement.yml +++ b/.github/workflows/dev-enforcement.yml @@ -1,62 +1,109 @@ name: Dev Workflow Enforcement + +# This gate had never run a single job. Every layer was broken independently, +# and each failed in a way that looked like the layer below it. +# +# 1. The YAML did not parse. In the pr-status job `steps:` was indented inside +# `if:`, and further down `run:` inside `if:`. GitHub compiled zero jobs and +# titled the run by its file path rather than by `name:` — which is what a +# red X on ".github/workflows/dev-enforcement.yml" means. +# 2. No job had an actions/checkout step, so `git log -1` ran outside a +# repository and .trinity/dev_session.json was never on disk. +# 3. `$COMMIT` was never assigned, so the issue-ID test read an empty string and +# could only ever print "Missing issue ID" — including for a correct commit. +# 4. The commit-format regex was `^\[a-f]+\(...`, which requires a literal `[` +# at the start of the subject. No conventional commit begins with one, and +# its result was piped to `head -1` and discarded regardless. +# 5. session-check gated CI on .trinity/dev_session.json being TESTED or +# COMMITTED. That is local working state; CI cannot observe a dev session, +# and on main it asserts whatever state happened to be committed. +# +# REPORTING, NOT BLOCKING — and that is a deliberate difference from the same +# gate in trinity-fpga, which does block on pull requests. +# +# Measured before choosing, on the last 60 non-merge commits of each main: +# +# trinity-fpga 3 / 60 ( 5%) would fail +# trinity 25 / 60 (42%) would fail +# +# This repository's practice is largely prose subjects, many in Russian +# ("Локализовать ветвь дерева технологий (#785)"), while CLAUDE.md documents +# `(): `. That gap is a policy question and not mine +# to settle by switching a gate to blocking: a check that fails 42% of the work +# is a check people route around. So it says what it sees, with counts, and the +# decision to tighten it stays with a reader who can also change the rule. + on: pull_request: types: [opened, synchronize] push: branches: [main] +permissions: + contents: read + pull-requests: read + jobs: - ci-check: + commit-format: name: CI Validation runs-on: ubuntu-latest steps: - - name: Validate Commit Format + # Depth 0 on a PR so every commit in it can be read, not just the tip. + # There was no checkout at all before, which is why `git log` failed. + - uses: actions/checkout@v4 + with: + fetch-depth: ${{ github.event_name == 'pull_request' && 0 || 1 }} + + - name: Commit subject format + env: + BASE: ${{ github.event.pull_request.base.sha }} + HEAD: ${{ github.event.pull_request.head.sha }} run: | - git log -1 --format=%s | grep -E '^\[a-f]+\((feat|fix|refactor|docs|chore|test|ci)\([^)]+\):' | head -1 - if [[ $(echo "$COMMIT" | grep -oE '(ISSUE-|#)[0-9]+') ]]; then - echo "✅ Valid issue ID format" + set -u + if [ -n "${BASE:-}" ] && [ -n "${HEAD:-}" ]; then + RANGE="$BASE..$HEAD" else - echo "❌ Missing issue ID (format: feat(scope): description (#123) or fix(scope): description (ISSUE-123))" - exit 1 + RANGE="-1" fi - session-check: - name: Verify Dev Session State - runs-on: ubuntu-latest - steps: - - name: Check session state before commit - run: | - if [ ! -f .trinity/dev_session.json ]; then - echo "❌ No dev session found" - exit 1 + # Merge subjects are generated by GitHub, not by an author, so + # holding them to the format would fail every merge. + SUBJECTS=$(git log --no-merges --format=%s $RANGE) + if [ -z "$SUBJECTS" ]; then + echo "no non-merge commits to check" + exit 0 fi - STATE=$(jq -r '.state' .trinity/dev_session.json 2>/dev/null || echo "IDLE") - ISSUE=$(jq -r '.issue_number' .trinity/dev_session.json 2>/dev/null || echo "0") + PATTERN='^(feat|fix|refactor|docs|chore|test|ci|perf|build|style)(\([a-z0-9._/-]+\))?!?: .+' + ok=0; bad=0 + while IFS= read -r s; do + if echo "$s" | grep -qE "$PATTERN"; then + ok=$((ok + 1)) + else + echo " off-format: $s" + bad=$((bad + 1)) + fi + done <<< "$SUBJECTS" - if [ "$STATE" != "TESTED" ] && [ "$STATE" != "COMMITTED" ]; then - echo "❌ Invalid state: $STATE (must be TESTED or COMMITTED)" - echo " Run: tri dev test to pass tests" - exit 1 + echo "$ok in CLAUDE.md format, $bad not" + if [ "$bad" -gt 0 ]; then + echo "::warning::$bad of $((ok + bad)) subject(s) are not (): " fi - if [ "$ISSUE" = "0" ]; then - echo "❌ No active issue" - exit 1 + - name: Issue reference + if: github.event_name == 'pull_request' + env: + BASE: ${{ github.event.pull_request.base.sha }} + HEAD: ${{ github.event.pull_request.head.sha }} + BODY: ${{ github.event.pull_request.body }} + TITLE: ${{ github.event.pull_request.title }} + run: | + set -u + BLOB="$(git log --no-merges --format='%s %b' "$BASE..$HEAD") + $TITLE + $BODY" + if echo "$BLOB" | grep -qoE '(ISSUE-|#)[0-9]+'; then + echo "issue reference found: $(echo "$BLOB" | grep -oE '(ISSUE-|#)[0-9]+' | sort -u | tr '\n' ' ')" + else + echo "::warning::no issue reference (#123 or ISSUE-123) in the commits, title or body" fi - - echo "✅ Session state valid: $STATE for issue #$ISSUE" - - pr-status: - name: Update PR Status - runs-on: ubuntu-latest - if: github.event_name == 'pull_request' - steps: - - name: Add in-progress label - run: | - gh pr edit "$PR_NUMBER" --add-label "status:in-progress" - - name: Update to done when merged - if: github.event.action == 'closed' && github.event.pull_request.merged == true - run: | - gh pr edit "$PR_NUMBER" --remove-label "status:in-progress" - gh pr edit "$PR_NUMBER" --add-label "status:completed" From fe5222b63377c77c43a7a2a4482b0da433032631 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasilev Date: Sat, 15 Aug 2026 19:01:35 +0700 Subject: [PATCH 2/2] =?UTF-8?q?fix(ci):=20fetch-depth=200=20unconditionall?= =?UTF-8?q?y=20=E2=80=94=20the=20ternary=20evaluated=20to=201=20on=20PRs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fetch-depth: ${{ github.event_name == 'pull_request' && 0 || 1 }} evaluates to 1 on a pull request, not 0: 'true && 0' is '0', '0' is falsy, so '|| 1' wins. The clone was shallow and the job died with fatal: Invalid revision range 1bbe926..59c991c which reads as a bad SHA rather than as a missing object. Caught on this PR's own first run. The idiom is only safe when the middle operand is truthy. Co-Authored-By: Claude Opus 5 --- .github/workflows/dev-enforcement.yml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dev-enforcement.yml b/.github/workflows/dev-enforcement.yml index 531e89485a..fa23e6ebfa 100644 --- a/.github/workflows/dev-enforcement.yml +++ b/.github/workflows/dev-enforcement.yml @@ -48,11 +48,19 @@ jobs: name: CI Validation runs-on: ubuntu-latest steps: - # Depth 0 on a PR so every commit in it can be read, not just the tip. - # There was no checkout at all before, which is why `git log` failed. + # Full history, unconditionally. There was no checkout at all before, + # which is why `git log` failed. + # + # The first version wrote + # fetch-depth: ${{ github.event_name == 'pull_request' && 0 || 1 }} + # which evaluates to 1 on a pull request, not 0: `true && 0` is `0`, `0` + # is falsy, so `|| 1` wins. The clone came out shallow and the job died + # with `fatal: Invalid revision range ..` — a range error that + # reads as a bad SHA rather than as a missing object. The ternary idiom is + # only safe when the middle operand is truthy. - uses: actions/checkout@v4 with: - fetch-depth: ${{ github.event_name == 'pull_request' && 0 || 1 }} + fetch-depth: 0 - name: Commit subject format env: