Fixes #703 #705
49
docs/scratchpads/703-wrapper-interactive-auth.md
Normal file
49
docs/scratchpads/703-wrapper-interactive-auth.md
Normal file
@@ -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
|
||||||
@@ -240,6 +240,33 @@ get_gitea_login_for_host() {
|
|||||||
return 1
|
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() {
|
get_default_tea_login() {
|
||||||
local logins_json
|
local logins_json
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ TITLE=""
|
|||||||
BODY=""
|
BODY=""
|
||||||
LABELS=""
|
LABELS=""
|
||||||
MILESTONE=""
|
MILESTONE=""
|
||||||
|
INTERACTIVE=false
|
||||||
|
|
||||||
# get_remote_host and get_gitea_token are provided by detect-platform.sh
|
# get_remote_host and get_gitea_token are provided by detect-platform.sh
|
||||||
|
|
||||||
@@ -66,11 +67,13 @@ Options:
|
|||||||
-b, --body BODY Issue body/description
|
-b, --body BODY Issue body/description
|
||||||
-l, --labels LABELS Comma-separated labels (e.g., "bug,feature")
|
-l, --labels LABELS Comma-separated labels (e.g., "bug,feature")
|
||||||
-m, --milestone NAME Milestone name to assign
|
-m, --milestone NAME Milestone name to assign
|
||||||
|
-i, --interactive Prompt for missing issue fields
|
||||||
-h, --help Show this help message
|
-h, --help Show this help message
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
$(basename "$0") -t "Fix login bug" -l "bug,priority-high"
|
$(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") -t "Add dark mode" -b "Implement theme switching" -m "0.2.0"
|
||||||
|
$(basename "$0") -i
|
||||||
EOF
|
EOF
|
||||||
exit "${1:-1}"
|
exit "${1:-1}"
|
||||||
}
|
}
|
||||||
@@ -94,6 +97,10 @@ while [[ $# -gt 0 ]]; do
|
|||||||
MILESTONE="$2"
|
MILESTONE="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
|
-i|--interactive)
|
||||||
|
INTERACTIVE=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
usage 0
|
usage 0
|
||||||
;;
|
;;
|
||||||
@@ -104,6 +111,13 @@ while [[ $# -gt 0 ]]; do
|
|||||||
esac
|
esac
|
||||||
done
|
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
|
if [[ -z "$TITLE" ]]; then
|
||||||
echo "Error: Title is required (-t)" >&2
|
echo "Error: Title is required (-t)" >&2
|
||||||
usage
|
usage
|
||||||
@@ -127,6 +141,11 @@ case "$PLATFORM" in
|
|||||||
gitea_issue_create_api
|
gitea_issue_create_api
|
||||||
exit $?
|
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")
|
REPO_ARGS=(--repo "$REPO_SLUG" --login "$GITEA_LOGIN_NAME")
|
||||||
CMD=(tea issue create "${REPO_ARGS[@]}" --title "$TITLE")
|
CMD=(tea issue create "${REPO_ARGS[@]}" --title "$TITLE")
|
||||||
[[ -n "$BODY" ]] && CMD+=(--description "$BODY")
|
[[ -n "$BODY" ]] && CMD+=(--description "$BODY")
|
||||||
|
|||||||
@@ -183,6 +183,11 @@ case "$PLATFORM" in
|
|||||||
gitea_pr_create_api
|
gitea_pr_create_api
|
||||||
exit $?
|
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")
|
REPO_ARGS=(--repo "$REPO_SLUG" --login "$GITEA_LOGIN_NAME")
|
||||||
CMD=(tea pr create "${REPO_ARGS[@]}" --title "$TITLE")
|
CMD=(tea pr create "${REPO_ARGS[@]}" --title "$TITLE")
|
||||||
[[ -n "$BODY" ]] && CMD+=(--description "$BODY")
|
[[ -n "$BODY" ]] && CMD+=(--description "$BODY")
|
||||||
|
|||||||
@@ -45,6 +45,11 @@ JSON
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ "${1:-}" == "api" ]]; then
|
||||||
|
printf '%s\n' '{"login":"ci-bot"}'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
printf 'tea %s\n' "$*" >> "$MOSAIC_TEST_LOG"
|
printf 'tea %s\n' "$*" >> "$MOSAIC_TEST_LOG"
|
||||||
if [[ "${MOSAIC_TEA_FAIL_PR_CREATE:-}" == "1" && "$*" == pr\ create* ]]; then
|
if [[ "${MOSAIC_TEA_FAIL_PR_CREATE:-}" == "1" && "$*" == pr\ create* ]]; then
|
||||||
echo 'GetUserByName: simulated stale login failure' >&2
|
echo 'GetUserByName: simulated stale login failure' >&2
|
||||||
|
|||||||
@@ -55,6 +55,11 @@ JSON
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ "${1:-}" == "api" ]]; then
|
||||||
|
printf '%s\n' '{"login":"ci-bot"}'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "${1:-}" == "issue" && "${2:-}" == "create" ]]; then
|
if [[ "${1:-}" == "issue" && "${2:-}" == "create" ]]; then
|
||||||
desc=""
|
desc=""
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
|
|||||||
88
packages/mosaic/framework/tools/git/test-issue-create-interactive-auth.sh
Executable file
88
packages/mosaic/framework/tools/git/test-issue-create-interactive-auth.sh
Executable file
@@ -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'
|
||||||
Reference in New Issue
Block a user