From b580d37d51bf3fc570dce27f70123fea2f2b2559 Mon Sep 17 00:00:00 2001 From: "jason.woltje" Date: Sun, 12 Jul 2026 20:49:43 +0000 Subject: [PATCH] Fixes #703 (#705) --- .../703-wrapper-interactive-auth.md | 49 +++++++++++ .../framework/tools/git/detect-platform.sh | 27 ++++++ .../framework/tools/git/issue-create.sh | 19 ++++ .../mosaic/framework/tools/git/pr-create.sh | 5 ++ .../tools/git/test-gitea-login-resolution.sh | 5 ++ .../git/test-issue-create-body-safety.sh | 5 ++ .../git/test-issue-create-interactive-auth.sh | 88 +++++++++++++++++++ 7 files changed, 198 insertions(+) create mode 100644 docs/scratchpads/703-wrapper-interactive-auth.md create mode 100755 packages/mosaic/framework/tools/git/test-issue-create-interactive-auth.sh diff --git a/docs/scratchpads/703-wrapper-interactive-auth.md b/docs/scratchpads/703-wrapper-interactive-auth.md new file mode 100644 index 0000000..cbc3720 --- /dev/null +++ b/docs/scratchpads/703-wrapper-interactive-auth.md @@ -0,0 +1,49 @@ +# #703 Git Wrapper Interactive and Auth Resilience + +## Objective + +Restore the deployed Git wrapper contract: issue-create supports interactive invocation and Gitea mutation behavior tolerates a stale Tea authenticated user by validating current identity and using the existing host-scoped API fallback. + +## Scope + +- `packages/mosaic/framework/tools/git/issue-create.sh` +- `packages/mosaic/framework/tools/git/detect-platform.sh` +- Git wrapper regression harnesses +- This scratchpad + +## Requirements / acceptance evidence + +1. `issue-create -i` and `--interactive` prompt for missing issue fields without exposing credentials. +2. Explicit command-line fields retain precedence and do not trigger prompt input. +3. Gitea wrapper resolves the current user dynamically from the target host and does not rely on the saved Tea user identity. +4. A Tea `GetUserByName` failure falls back to authenticated API creation. +5. Existing body-safety, login-resolution, issue-create, and pr-create paths remain green. +6. Source framework is re-seeded to deployed `~/.config/mosaic`, then deployed wrappers are verified end to end. + +## Plan + +1. Add failing shell regression harness for interactive input and stale Tea user fallback. +2. Implement minimal helper and parser changes. +3. Run wrapper harnesses, syntax checks, and repository baseline checks. +4. Re-seed deployed framework and run live wrapper verification. +5. Commit, queue guard, push, open PR, and stop for independent review. + +## Progress + +- Issue #703 filed before code; issue comment records #536 root cause and stale-login trigger. +- Deployed wrapper `issue-create.sh -i` reproduced: `Unknown option: -i` (exit 1). +- Live Tea mutation did not reproduce `GetUserByName` on this host because the current mosaicstack Tea login is valid. The test harness models the reported stale authenticated-user condition. +- Implemented `-i` / `--interactive` prompt collection and a dynamic Tea `/user` validation. A stale Tea identity now selects the existing host-scoped Gitea API fallback before mutation for both issue and PR creation. +- Re-seeded the framework with `MOSAIC_INSTALL_MODE=keep MOSAIC_SYNC_ONLY=1 bash packages/mosaic/framework/install.sh`. Installed and source wrapper SHA-256 values matched. +- Live deployed verification: interactive issue-create opened then closed #704; installed dynamic identity resolved `jason.woltje`. + +## Verification + +- PASS: `packages/mosaic/framework/tools/git/test-issue-create-interactive-auth.sh` +- PASS: `packages/mosaic/framework/tools/git/test-issue-create-body-safety.sh` +- PASS: `packages/mosaic/framework/tools/git/test-gitea-login-resolution.sh` +- PASS: `packages/mosaic/framework/tools/git/test-pr-metadata-gitea.sh` +- PASS: `packages/mosaic/framework/tools/git/test-pr-merge-gitea-empty-uid.sh` +- PASS: `bash packages/mosaic/framework/tools/git/test-lane-brief-pr-linkage.sh` +- PASS: `bash -n packages/mosaic/framework/tools/git/*.sh` +- PASS: Prettier check for this scratchpad diff --git a/packages/mosaic/framework/tools/git/detect-platform.sh b/packages/mosaic/framework/tools/git/detect-platform.sh index 626e1a0..7519f11 100755 --- a/packages/mosaic/framework/tools/git/detect-platform.sh +++ b/packages/mosaic/framework/tools/git/detect-platform.sh @@ -240,6 +240,33 @@ get_gitea_login_for_host() { return 1 } +# Validate the current authenticated Gitea user for a resolved Tea login. +# Tea stores a user name with each login which can become stale after user rename, +# token rotation, or server migration. Querying /user derives the identity from the +# active credential instead of trusting that saved name. Callers fall back to the +# host-scoped API path when this validation fails. +get_gitea_authenticated_user() { + local login_name="$1" response + + command -v tea >/dev/null 2>&1 || return 1 + response=$(tea api --login "$login_name" /user 2>/dev/null) || return 1 + TEA_AUTHENTICATED_USER_JSON="$response" python3 - <<'PY' +import json +import os + +try: + user = json.loads(os.environ["TEA_AUTHENTICATED_USER_JSON"]) +except (KeyError, json.JSONDecodeError): + raise SystemExit(1) + +login = user.get("login") if isinstance(user, dict) else None +if isinstance(login, str) and login: + print(login) + raise SystemExit(0) +raise SystemExit(1) +PY +} + get_default_tea_login() { local logins_json diff --git a/packages/mosaic/framework/tools/git/issue-create.sh b/packages/mosaic/framework/tools/git/issue-create.sh index f92f976..96f890c 100755 --- a/packages/mosaic/framework/tools/git/issue-create.sh +++ b/packages/mosaic/framework/tools/git/issue-create.sh @@ -12,6 +12,7 @@ TITLE="" BODY="" LABELS="" MILESTONE="" +INTERACTIVE=false # get_remote_host and get_gitea_token are provided by detect-platform.sh @@ -66,11 +67,13 @@ Options: -b, --body BODY Issue body/description -l, --labels LABELS Comma-separated labels (e.g., "bug,feature") -m, --milestone NAME Milestone name to assign + -i, --interactive Prompt for missing issue fields -h, --help Show this help message Examples: $(basename "$0") -t "Fix login bug" -l "bug,priority-high" $(basename "$0") -t "Add dark mode" -b "Implement theme switching" -m "0.2.0" + $(basename "$0") -i EOF exit "${1:-1}" } @@ -94,6 +97,10 @@ while [[ $# -gt 0 ]]; do MILESTONE="$2" shift 2 ;; + -i|--interactive) + INTERACTIVE=true + shift + ;; -h|--help) usage 0 ;; @@ -104,6 +111,13 @@ while [[ $# -gt 0 ]]; do esac done +if [[ "$INTERACTIVE" == true ]]; then + [[ -n "$TITLE" ]] || read -r -p "Issue title: " TITLE + [[ -n "$BODY" ]] || read -r -p "Issue body (optional): " BODY || true + [[ -n "$LABELS" ]] || read -r -p "Labels, comma-separated (optional): " LABELS || true + [[ -n "$MILESTONE" ]] || read -r -p "Milestone (optional): " MILESTONE || true +fi + if [[ -z "$TITLE" ]]; then echo "Error: Title is required (-t)" >&2 usage @@ -127,6 +141,11 @@ case "$PLATFORM" in gitea_issue_create_api exit $? } + if ! get_gitea_authenticated_user "$GITEA_LOGIN_NAME" >/dev/null; then + echo "Warning: Tea authenticated-user validation failed (possible stale user/login); trying Gitea API fallback..." >&2 + gitea_issue_create_api + exit $? + fi REPO_ARGS=(--repo "$REPO_SLUG" --login "$GITEA_LOGIN_NAME") CMD=(tea issue create "${REPO_ARGS[@]}" --title "$TITLE") [[ -n "$BODY" ]] && CMD+=(--description "$BODY") diff --git a/packages/mosaic/framework/tools/git/pr-create.sh b/packages/mosaic/framework/tools/git/pr-create.sh index 9560f1d..46b82a6 100755 --- a/packages/mosaic/framework/tools/git/pr-create.sh +++ b/packages/mosaic/framework/tools/git/pr-create.sh @@ -183,6 +183,11 @@ case "$PLATFORM" in gitea_pr_create_api exit $? } + if ! get_gitea_authenticated_user "$GITEA_LOGIN_NAME" >/dev/null; then + echo "Warning: Tea authenticated-user validation failed (possible stale user/login); trying Gitea API fallback..." >&2 + gitea_pr_create_api + exit $? + fi REPO_ARGS=(--repo "$REPO_SLUG" --login "$GITEA_LOGIN_NAME") CMD=(tea pr create "${REPO_ARGS[@]}" --title "$TITLE") [[ -n "$BODY" ]] && CMD+=(--description "$BODY") diff --git a/packages/mosaic/framework/tools/git/test-gitea-login-resolution.sh b/packages/mosaic/framework/tools/git/test-gitea-login-resolution.sh index b767084..4eb495a 100755 --- a/packages/mosaic/framework/tools/git/test-gitea-login-resolution.sh +++ b/packages/mosaic/framework/tools/git/test-gitea-login-resolution.sh @@ -45,6 +45,11 @@ JSON exit 0 fi +if [[ "${1:-}" == "api" ]]; then + printf '%s\n' '{"login":"ci-bot"}' + exit 0 +fi + printf 'tea %s\n' "$*" >> "$MOSAIC_TEST_LOG" if [[ "${MOSAIC_TEA_FAIL_PR_CREATE:-}" == "1" && "$*" == pr\ create* ]]; then echo 'GetUserByName: simulated stale login failure' >&2 diff --git a/packages/mosaic/framework/tools/git/test-issue-create-body-safety.sh b/packages/mosaic/framework/tools/git/test-issue-create-body-safety.sh index 4366020..1c95c3c 100755 --- a/packages/mosaic/framework/tools/git/test-issue-create-body-safety.sh +++ b/packages/mosaic/framework/tools/git/test-issue-create-body-safety.sh @@ -55,6 +55,11 @@ JSON exit 0 fi +if [[ "${1:-}" == "api" ]]; then + printf '%s\n' '{"login":"ci-bot"}' + exit 0 +fi + if [[ "${1:-}" == "issue" && "${2:-}" == "create" ]]; then desc="" while [[ $# -gt 0 ]]; do diff --git a/packages/mosaic/framework/tools/git/test-issue-create-interactive-auth.sh b/packages/mosaic/framework/tools/git/test-issue-create-interactive-auth.sh new file mode 100755 index 0000000..496e2c4 --- /dev/null +++ b/packages/mosaic/framework/tools/git/test-issue-create-interactive-auth.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +# Regression harness for #703: interactive issue creation and stale Tea-user fallback. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +WORK_DIR="${MOSAIC_TEST_WORK_DIR:-$PWD/.mosaic-test-work/issue-create-interactive-auth}" +REPO_DIR="$WORK_DIR/repo" +BIN_DIR="$WORK_DIR/bin" +LOG_FILE="$WORK_DIR/calls.log" +CREDENTIALS_FILE="$WORK_DIR/credentials.json" + +rm -rf "$WORK_DIR" +mkdir -p "$REPO_DIR" "$BIN_DIR" +git -C "$REPO_DIR" init -q +git -C "$REPO_DIR" remote add origin https://git.mosaicstack.dev/mosaicstack/stack.git + +cat > "$CREDENTIALS_FILE" <<'JSON' +{"gitea":{"mosaicstack":{"url":"https://git.mosaicstack.dev","token":"test-token"}}} +JSON + +cat > "$BIN_DIR/tea" <<'SH' +#!/usr/bin/env bash +set -euo pipefail +if [[ "$*" == "login list --output json" ]]; then + printf '%s\n' '[{"name":"mosaicstack","url":"https://git.mosaicstack.dev"}]' + exit 0 +fi +if [[ "${1:-}" == "api" ]]; then + if [[ "${MOSAIC_TEA_STALE_USER:-0}" == "1" ]]; then + echo 'GetUserByName: stale configured user' >&2 + exit 1 + fi + printf '%s\n' '{"login":"current-user"}' + exit 0 +fi +printf 'tea %s\n' "$*" >> "$MOSAIC_TEST_LOG" +exit 0 +SH + +cat > "$BIN_DIR/curl" <<'SH' +#!/usr/bin/env bash +set -euo pipefail +printf 'curl %s\n' "$*" >> "$MOSAIC_TEST_LOG" +printf '%s\n' '{"number":703}' +SH +chmod +x "$BIN_DIR/tea" "$BIN_DIR/curl" + +run_wrapper() { + ( + cd "$REPO_DIR" + PATH="$BIN_DIR:$PATH" \ + MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \ + MOSAIC_TEST_LOG="$LOG_FILE" \ + "$@" + ) +} + +: > "$LOG_FILE" +printf 'Interactive title\nInteractive body\nlabel-a,label-b\nM1\n' | run_wrapper "$SCRIPT_DIR/issue-create.sh" -i >/dev/null + +grep -q -- 'tea issue create --repo mosaicstack/stack --login mosaicstack --title Interactive title --description Interactive body --labels label-a,label-b --milestone M1' "$LOG_FILE" + +# Explicit values take precedence in interactive mode: no title input is +# supplied, but the wrapper still creates the issue with the explicit title. +: > "$LOG_FILE" +printf '\n\n\n' | run_wrapper "$SCRIPT_DIR/issue-create.sh" -i -t 'Explicit title' >/dev/null +grep -q -- 'tea issue create --repo mosaicstack/stack --login mosaicstack --title Explicit title' "$LOG_FILE" + +: > "$LOG_FILE" +run_wrapper env MOSAIC_TEA_STALE_USER=1 "$SCRIPT_DIR/issue-create.sh" -t 'Fallback title' -b 'Fallback body' >/dev/null 2>"$WORK_DIR/issue-stderr" +grep -q -- 'curl .*https://git.mosaicstack.dev/api/v1/repos/mosaicstack/stack/issues' "$LOG_FILE" +grep -q -- 'Tea authenticated-user validation failed' "$WORK_DIR/issue-stderr" +if grep -q -- 'tea issue create' "$LOG_FILE"; then + echo 'FAIL: issue-create invoked Tea mutation after stale-user validation failed' >&2 + exit 1 +fi + +: > "$LOG_FILE" +run_wrapper env MOSAIC_TEA_STALE_USER=1 "$SCRIPT_DIR/pr-create.sh" -t 'PR fallback' -H feature/wrapfix >/dev/null 2>"$WORK_DIR/pr-stderr" +grep -q -- 'curl .*https://git.mosaicstack.dev/api/v1/repos/mosaicstack/stack/pulls' "$LOG_FILE" +grep -q -- 'Tea authenticated-user validation failed' "$WORK_DIR/pr-stderr" +if grep -q -- 'tea pr create' "$LOG_FILE"; then + echo 'FAIL: pr-create invoked Tea mutation after stale-user validation failed' >&2 + exit 1 +fi + +echo 'issue-create interactive/auth regression harness passed'