From 4df9e54d5cfd8a8bdd82d7178de8c90f5b5ff457 Mon Sep 17 00:00:00 2001 From: Cartago Date: Thu, 10 Sep 2026 21:10:07 +0200 Subject: [PATCH] feat: adopt shared-checkout-pr, and let the protection check read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tanit's `develop` and `main` have been protected and requiring `ci-summary` for a while, but its `delendai.config.json` carried no `development` block — so the resolved policy still fell through the legacy compatibility layer to `shared-direct`. The repository enforced one model and the configuration described another. It now selects `shared-checkout-pr` explicitly, with `ci-summary` as the required check, 0 approvals on the integration branch and 1 on the release branch: certified work integrates autonomously, promoting to a release keeps a human in the loop. Resolved and validated against the policy engine before committing — zero violations, `usesWipRefs: true`. The `branch-protection` job has been failing on every run, and not because the protection was wrong. It passed `github.token`, which cannot read branch protection at all — that needs Administration: read. It now prefers `BRANCH_PROTECTION_TOKEN`, a fine-grained READ-ONLY token, and falls back to the default. Deliberately not an admin token: a check that can rewrite the rules it verifies is not a check. The script conflated three different outcomes, which is why the failure was unreadable: - 404 on `/protection` reported `required_status_checks.contexts ausente`. It means the branch has NO rule, and saying it that way sent a reader to fix a field inside a protection that does not exist. - 401/403 was reported as "GitHub rechazó la protección" — as though the protection were wrong, when in fact nothing was read. It stays a failure, because a check that could not verify a property cannot certify it either, but it now names the cause and the remedy. Co-Authored-By: Claude Opus 5 --- .github/workflows/validate.yml | 10 +++++++++- delendai.config.json | 9 +++++++++ scripts/gates/branch-protection.script.ts | 18 +++++++++++++++++- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 6d45b31c..48a14fbe 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -219,9 +219,17 @@ jobs: bun-version: 1.4.2 - name: Install dependencies run: bun install --frozen-lockfile + # `github.token` cannot read branch protection — that needs + # Administration: read, which the default token does not carry. So + # this job failed on every run for lack of permission, and reported + # it as though the protection itself were wrong. + # + # `BRANCH_PROTECTION_TOKEN` is a fine-grained, READ-ONLY token + # scoped to this repository. It is deliberately not an admin token: + # a check that can rewrite the rules it verifies is not a check. - name: Verify develop branch protection env: - GITHUB_TOKEN: ${{ github.token }} + GITHUB_TOKEN: ${{ secrets.BRANCH_PROTECTION_TOKEN || github.token }} GITHUB_REPOSITORY: ${{ github.repository }} run: bun run ci:branch-protection diff --git a/delendai.config.json b/delendai.config.json index 0b8df2fd..55b4b649 100644 --- a/delendai.config.json +++ b/delendai.config.json @@ -1,5 +1,14 @@ { "coreVersion": "0.1.0", + "development": { + "profile": "shared-checkout-pr", + "branches": { "integration": "develop", "release": "main" }, + "integration": { + "requiredChecks": ["ci-summary"], + "requiredApprovals": 0, + "releaseRequiredApprovals": 1 + } + }, "cacheDir": ".cache/delendai", "docsDir": "docs/delendai", "surfaceMode": "managed", diff --git a/scripts/gates/branch-protection.script.ts b/scripts/gates/branch-protection.script.ts index 13bfebc0..eab95aaf 100644 --- a/scripts/gates/branch-protection.script.ts +++ b/scripts/gates/branch-protection.script.ts @@ -252,10 +252,26 @@ async function checkBranchProtection( options.token, ); if (protectionResponse.status === 404) { + // 404 en `/protection` significa "la rama no tiene regla", no + // "faltan contexts". Decirlo mal manda a corregir un campo dentro + // de una protección que no existe. return { branch: options.branch, ok: false, - detail: `required_status_checks.contexts ausente`, + detail: `la rama no tiene ninguna regla de protección`, + }; + } + if (protectionResponse.status === 401 || protectionResponse.status === 403) { + // NO es lo mismo que "la protección está mal". No se ha leído nada, + // así que no se concluye nada sobre ella: leer branch protection + // exige Administration: read, y el token por defecto de Actions no + // lo tiene. Un check que no pudo verificar una propiedad tampoco + // puede certificarla, así que sigue siendo un fallo — pero con el + // motivo correcto y el remedio a mano. + return { + branch: options.branch, + ok: false, + detail: `no se pudo leer la protección (HTTP ${protectionResponse.status}); nada se concluye de ello. Configura BRANCH_PROTECTION_TOKEN con Administration: read`, }; } if (!protectionResponse.ok) {