diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b4968bb..969f1e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,11 +24,13 @@ Three separate GitHub identities are used so each role is cleanly separated: | Account | Purpose | Credentials stored as | | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | `gnovak` | Repo owner. Used for normal development and testing. | `RDB_PAT_TOKEN` (on both repos), or GitHub App (`RDB_APP_ID` variable + `RDB_APP_PRIVATE_KEY` secret) | -| `remote-dev-bot` | Dedicated bot account. Collaborator on `remote-dev-bot-test`. Posts authorized test comments that trigger agent runs without attributing activity to `gnovak`. | `RDB_TESTER_PAT_TOKEN` (on remote-dev-bot) | +| `remote-dev-bot` | Dedicated bot account. Collaborator on `remote-dev-bot-test`. Posts authorized test comments that trigger agent runs without attributing activity to `gnovak`. | `RDB_TESTER_PAT_TOKEN` (on remote-dev-bot) — but see the temporary-state note below | | `remote-dev-bot-tester` | Simulates an unauthorized external user. NOT a collaborator on any repo. | `RDB_TESTER_UNAUTHORIZED_PAT_TOKEN` (on remote-dev-bot) | ### remote-dev-bot (bot account) details +The intended setup: + - Classic PAT with `public_repo` + `workflow` scopes, no expiration - **`public_repo`** — create issues, post comments, open PRs in public repos (standard e2e test flow) @@ -40,6 +42,17 @@ Three separate GitHub identities are used so each role is cleanly separated: its trigger comments - Keeps test activity out of `gnovak`'s GitHub contribution stats +> **Temporary state (as of 2026-08):** the `remote-dev-bot` account is +> flagged by GitHub (its profile 404s on the public API), so tokens cannot +> be minted on it until that's resolved with GitHub support. Until then, +> `RDB_TESTER_PAT_TOKEN` holds a PAT minted on `gnovak` instead (verifiable: +> e2e test issues on rdb-test are authored by `gnovak`). If minting the +> stopgap on `gnovak`, prefer a fine-grained PAT scoped to +> `gnovak/remote-dev-bot-test` only (Contents/Issues/Pull requests +> read-write + Actions read-write) with a long expiry — the 2026-08-05 +> full-suite failure was a 90-day stopgap token quietly expiring. Once the +> bot account is restored, move the token back per the intended setup above. + ### remote-dev-bot-tester details - Classic PAT with `public_repo` scope, no expiration @@ -81,7 +94,7 @@ Secrets stored on `gnovak/remote-dev-bot`: | `OPENAI_API_KEY` | OpenAI API key (for GPT models) | | `GEMINI_API_KEY` | Google AI API key (for Gemini models) | | `RDB_APP_PRIVATE_KEY` | GitHub App private key, for bot identity on comments/PRs. | -| `RDB_TESTER_PAT_TOKEN` | PAT for `remote-dev-bot` account (collaborator on rdb-test). Used by e2e tests to post authorized trigger comments. | +| `RDB_TESTER_PAT_TOKEN` | PAT for `remote-dev-bot` account (collaborator on rdb-test; temporarily a `gnovak` PAT — see account details). Used by e2e tests to post authorized trigger comments. | | `RDB_TESTER_UNAUTHORIZED_PAT_TOKEN` | PAT for `remote-dev-bot-tester` (not a collaborator). Used by security e2e tests to verify unauthorized users are blocked. | Variables stored on `gnovak/remote-dev-bot`: diff --git a/tests/e2e-security.sh b/tests/e2e-security.sh index dba73e6..f7e3ca5 100755 --- a/tests/e2e-security.sh +++ b/tests/e2e-security.sh @@ -58,6 +58,10 @@ check_graphql_quota() { fi remaining=$(echo "$result" | cut -d' ' -f1) reset_ts=$(echo "$result" | cut -d' ' -f2) + if ! [[ "$remaining" =~ ^[0-9]+$ ]]; then + echo "==> Warning: could not parse GraphQL rate limit response — proceeding anyway" + return + fi reset_time=$(python3 -c " import datetime ts = ${reset_ts} @@ -74,6 +78,31 @@ print(f'{local_t.strftime(\"%H:%M:%S\")} local / {utc_t.strftime(\"%H:%M:%S\")} echo "==> GraphQL quota: ${remaining} points remaining (resets ${reset_time})." } +# Fail fast with a clear message when GH_TOKEN is dead — an expired +# RDB_TESTER_PAT_TOKEN otherwise surfaces as confusing 401s on every +# issue-creation call after the suite has already started. +gh_login=$(gh api user --jq .login 2>/dev/null) || { + echo "ERROR: GH_TOKEN is invalid or expired (gh api user failed)." >&2 + echo "ERROR: In CI this token comes from secrets.RDB_TESTER_PAT_TOKEN —" >&2 + echo "ERROR: mint a new PAT with write access to the test repo and update the secret." >&2 + exit 1 +} +gh_exp=$(gh api user -i 2>/dev/null | grep -i "^github-authentication-token-expiration:" | cut -d" " -f2- | tr -d "\r") +echo "==> Authenticated to GitHub as: ${gh_login}${gh_exp:+ (token expires: ${gh_exp})}" + +# Validate the unauthorized-user token too, when present — its failures +# otherwise surface only mid-gating-test. +if [[ -n "${UNAUTHORIZED_PAT:-}" ]]; then + ua_login=$(curl -fs -H "Authorization: token ${UNAUTHORIZED_PAT}" \ + https://api.github.com/user 2>/dev/null | python3 -c "import json,sys; print(json.load(sys.stdin)['login'])" 2>/dev/null) || true + if [[ -z "${ua_login:-}" ]]; then + echo "ERROR: UNAUTHORIZED_PAT is invalid or expired (api.github.com/user failed)." >&2 + echo "ERROR: In CI this is secrets.RDB_TESTER_UNAUTHORIZED_PAT_TOKEN (remote-dev-bot-tester account)." >&2 + exit 1 + fi + echo "==> Unauthorized-user token authenticates as: ${ua_login}" +fi + check_graphql_quota # --- Helpers --- diff --git a/tests/e2e.sh b/tests/e2e.sh index 4331471..2c2b255 100755 --- a/tests/e2e.sh +++ b/tests/e2e.sh @@ -54,6 +54,10 @@ check_graphql_quota() { fi remaining=$(echo "$result" | cut -d' ' -f1) reset_ts=$(echo "$result" | cut -d' ' -f2) + if ! [[ "$remaining" =~ ^[0-9]+$ ]]; then + echo "==> Warning: could not parse GraphQL rate limit response — proceeding anyway" + return + fi reset_time=$(python3 -c " import datetime ts = ${reset_ts} @@ -70,6 +74,18 @@ print(f'{local_t.strftime(\"%H:%M:%S\")} local / {utc_t.strftime(\"%H:%M:%S\")} echo "==> GraphQL quota: ${remaining} points remaining (resets ${reset_time})." } +# Fail fast with a clear message when GH_TOKEN is dead — an expired +# RDB_TESTER_PAT_TOKEN otherwise surfaces as confusing 401s on every +# issue-creation call after the suite has already started. +gh_login=$(gh api user --jq .login 2>/dev/null) || { + echo "ERROR: GH_TOKEN is invalid or expired (gh api user failed)." >&2 + echo "ERROR: In CI this token comes from secrets.RDB_TESTER_PAT_TOKEN —" >&2 + echo "ERROR: mint a new PAT with write access to the test repo and update the secret." >&2 + exit 1 +} +gh_exp=$(gh api user -i 2>/dev/null | grep -i "^github-authentication-token-expiration:" | cut -d" " -f2- | tr -d "\r") +echo "==> Authenticated to GitHub as: ${gh_login}${gh_exp:+ (token expires: ${gh_exp})}" + check_graphql_quota # --- Test case definitions ---