diff --git a/.claude/scripts/maintainer-preflight.test.sh b/.claude/scripts/maintainer-preflight.test.sh index 5db3213c..7ca42fbe 100755 --- a/.claude/scripts/maintainer-preflight.test.sh +++ b/.claude/scripts/maintainer-preflight.test.sh @@ -38,9 +38,37 @@ grep -Fq 'classify the saved login as indeterminate' "${run_loop}" || grep -Fq 'authentication verification unavailable' "${run_loop}" || fail "missing the unavailable host-verification classification" -grep -Fq 'Only an explicit credential rejection from that host-level check proves the saved login invalid.' "${run_loop}" || +grep -Fq 'Only an explicit credential rejection' "${run_loop}" || fail "missing the host-confirmed invalid classification" +grep -Fq 'GitHub service degraded' "${run_loop}" || + fail "missing the REST 5xx / service-degraded classification" + +grep -Fq "gh api graphql --hostname github.com -f query='{viewer{login}}'" "${run_loop}" || + fail "missing the authenticated GraphQL viewer.login fallback pinned to github.com" + +grep -Fq 'rate-limited 403/429' "${run_loop}" || + fail "missing the rate-limit-as-service-degradation classification" + +grep -Fq 'same host and credential context' "${run_loop}" || + fail "GraphQL fallback does not preserve the failing probe's credential context" + +grep -Fq 'HTTP **401**' "${run_loop}" || + fail "missing the explicit HTTP 401 authentication-rejection criterion" + +grep -Fq 'non-rate-limit' "${run_loop}" || + fail "missing the non-rate-limit 403 credential-rejection criterion" + +grep -Fq 'A REST 5xx (or' "${run_loop}" || + fail "missing the REST-503-plus-GraphQL-success regression rule" + +grep -Fq 'recommend' "${run_loop}" && grep -Fq 'gh auth login' "${run_loop}" || + fail "missing the gh-auth-login-only-on-confirmed-rejection handoff rule" + +# The handoff must be gated on confirmed rejection — not on every auth-status failure. +grep -Fq 'and **only then** recommend' "${run_loop}" || + fail "missing the confirmed-rejection gate before recommending gh auth login" + grep -Fq 'record only these gate classifications in durable memory' "${run_loop}" || fail "missing the credential-safe memory rule" diff --git a/.claude/skills/portfolio-maintenance/SKILL.md b/.claude/skills/portfolio-maintenance/SKILL.md index ffe06e49..fac789be 100644 --- a/.claude/skills/portfolio-maintenance/SKILL.md +++ b/.claude/skills/portfolio-maintenance/SKILL.md @@ -57,14 +57,29 @@ card. If only the host-level saved-login check succeeds, run every subsequent `gh` command through that approved host-level execution path. Clearing the injected tokens does not make a sandboxed macOS Keychain readable. - Only an explicit credential rejection from that host-level check proves the saved login invalid. - If the host-level check instead authenticates a different account, hard-block as `wrong GitHub identity` - without describing the credential as invalid. - If the host-level check cannot run or fails - for a transport reason, hard-block as `authentication verification unavailable` instead of instructing - the maintainer to replace a credential that was never tested. Keep the injected-token result, saved-login - result, and `git fetch` result as separate gates, because repository reachability cannot prove GitHub API - identity (and vice versa); record only these gate classifications in durable memory, never credential output. + **Distinguish authentication rejection from GitHub service degradation** (monorepo#2206): a REST + `/user` (or `gh auth status`) probe that returns HTTP 5xx, HTML, other non-JSON service noise, **or + a rate-limited 403/429** (GitHub may return either status when the limit is exceeded — check + `x-ratelimit-*` headers or a rate-limit message body) is **not** proof the credential is bad — + classify that outcome as `GitHub service degraded` and run a bounded authenticated GraphQL + fallback against the **same host and credential context** as the failing probe: + `gh api graphql --hostname github.com -f query='{viewer{login}}'`. Prefix with + `env -u GH_TOKEN -u GITHUB_TOKEN` **only when the failing probe itself was the cleared-env + saved-login check**; otherwise keep the injected `GH_TOKEN`/`GITHUB_TOKEN` so a transient REST + failure cannot be misread as a bad keychain login. Always pass `--hostname github.com` so + `GH_HOST` cannot redirect the fallback to an unrelated enterprise host. Accept the identity when + GraphQL returns `devantler`. + Only an explicit credential rejection proves the login invalid — meaning HTTP **401**, a **non-rate-limit** + 403 that is clearly a credential/permission rejection (never a rate-limit 403), or `gh` reporting + the token rejected / not logged in — and **only then** recommend `gh auth login`. A REST 5xx (or + rate-limit) with a successful GraphQL `viewer.login` must never be reported as an invalid saved + login. If the host-level check instead authenticates a different account, hard-block as + `wrong GitHub identity` without describing the credential as invalid. + If the host-level check cannot run or fails for a transport reason (and the GraphQL fallback is + likewise unreachable), hard-block as `authentication verification unavailable` instead of + instructing the maintainer to replace a credential that was never tested. Keep the injected-token + result, saved-login result, and `git fetch` result as separate gates, because repository + reachability cannot prove GitHub API identity (and vice versa); record only these gate classifications in durable memory, never credential output. 3. **Check the boot memory surface fits in one read — BEFORE you read it.** A boot-loaded file past the Read cap is **truncated silently**: the run continues on a partial cursor with no signal that carry-forwards, stand-down notes, or `HANDS-OFF` records beyond the cut are missing (the 2026-06-05 blinding;