From 99dbd8cfb63bfbd10cae5cdd4d578d38544fbe97 Mon Sep 17 00:00:00 2001 From: code-be-02 Date: Sun, 23 Aug 2026 21:19:02 -0500 Subject: [PATCH] =?UTF-8?q?fix(git-tools):=20WP5a=20rework=20=E2=80=94=20a?= =?UTF-8?q?uthoritative=20curl=20status,=20string-validated=20base,=20CI?= =?UTF-8?q?=20enumeration=20(E4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit T51P2WP5AR B1-B3 + I1 on the default-base fallback: B1: test-pr-create-fallback-default-base.sh is now enumerated on the canonical S1 surface (packages/mosaic package.json test:framework-shell, git-tools cluster). Enumeration guard: population 67, enumerated 53, excluded 15 — green; the meta-test passes 14/14. PR pipeline 2623's sanitization failure clears on the next run. B2: gitea_default_branch fetches and parses as separate steps. The old `curl | python` pipeline reported only python's status, so an HTTP failure that still emitted parseable JSON masqueraded as success and POSTed. curl's exit status is now authoritative (rc nonzero -> failed resolution, loud -B remedy, no POST). Fixture: valid JSON + curl rc 22. B3: default_branch is valid only as a nonblank JSON string. null (which printed as the literal "None"), numbers, and whitespace-only strings were all POSTed as bases. The python step now type- and blank-checks; null / numeric / blank fixtures each produce rc 1, loud -B remedy, no POST. I1: --help now says the default base is the forge repository's default branch (was main/master, the removed behavior). Suite: 29 asserts (16 prior + 4 B2 + 9 B3), green x2. Sweep 32/33; test-issue-close-fail-closed.sh fails identically at baseline origin/next (pre-existing, untouched). --- .../mosaic/framework/tools/git/pr-create.sh | 27 ++++++++-- .../test-pr-create-fallback-default-base.sh | 50 ++++++++++++++++--- packages/mosaic/package.json | 2 +- 3 files changed, 67 insertions(+), 12 deletions(-) diff --git a/packages/mosaic/framework/tools/git/pr-create.sh b/packages/mosaic/framework/tools/git/pr-create.sh index 8af94abe..ea4524b7 100755 --- a/packages/mosaic/framework/tools/git/pr-create.sh +++ b/packages/mosaic/framework/tools/git/pr-create.sh @@ -23,16 +23,33 @@ gitea_default_branch() { # Forge default branch for the current repo (T51-P2 WP5a / spec E4): the # API fallback must not guess a base. Empty output or any lookup failure # returns nonzero so the caller fails loud instead of mistargeting a PR. - local host repo token url branch + local host repo token url body branch host=$(get_remote_host) || return 1 repo=$(get_repo_info) || return 1 token=$(get_gitea_token "$host") || return 1 url="https://${host}/api/v1/repos/${repo}" - branch=$(curl -fsS \ + # Fetch and parse as separate steps (T51P2WP5AR B2): a piped + # `curl | python` reports only python's status, so an HTTP failure that + # still emits parseable JSON would masquerade as success. curl's own + # exit status is authoritative here. + if ! body=$(curl -fsS \ -H "User-Agent: curl/8" \ -H "Authorization: token ${token}" \ - "$url" \ - | python3 -c 'import json,sys; print(json.load(sys.stdin).get("default_branch",""))' 2>/dev/null) || return 1 + "$url" 2>/dev/null); then + return 1 + fi + # A valid base is a NONBLANK JSON STRING (T51P2WP5AR B3): null, numbers, + # and whitespace-only values are failed resolution, never a POSTed base. + branch=$(printf '%s' "$body" | python3 -c ' +import json, sys +try: + value = json.load(sys.stdin).get("default_branch") +except Exception: + sys.exit(1) +if not isinstance(value, str) or not value.strip(): + sys.exit(1) +print(value.strip()) +' 2>/dev/null) || return 1 [[ -n "$branch" ]] || return 1 printf '%s' "$branch" } @@ -104,7 +121,7 @@ Create a pull request on the current repository (Gitea or GitHub). Options: -t, --title TITLE PR title (required, or use --issue) -b, --body BODY PR description/body - -B, --base BRANCH Base branch to merge into (default: main/master) + -B, --base BRANCH Base branch to merge into (default: the forge repository's default branch) -H, --head BRANCH Head branch with changes (default: current branch) -l, --labels LABELS Comma-separated labels -m, --milestone NAME Milestone name diff --git a/packages/mosaic/framework/tools/git/test-pr-create-fallback-default-base.sh b/packages/mosaic/framework/tools/git/test-pr-create-fallback-default-base.sh index 5d05eec9..2e18b182 100755 --- a/packages/mosaic/framework/tools/git/test-pr-create-fallback-default-base.sh +++ b/packages/mosaic/framework/tools/git/test-pr-create-fallback-default-base.sh @@ -54,12 +54,17 @@ mode="\${CURL_STUB_GET_MODE:-ok}" printf '%s\n' "\$*" >> "$ROOT/curl-calls.log" url="\${!#}" if [[ "\$url" == */api/v1/repos/acme/widgets ]]; then - # repo GET (default-branch resolution) - if [[ "\$mode" != "ok" ]]; then - echo "curl stub: simulated repo lookup failure" >&2 - exit 1 - fi - printf '%s\n' '{"id":1,"default_branch":"next","full_name":"acme/widgets"}' + # repo GET (default-branch resolution). Modes cover the value shapes the + # resolver must accept or refuse (T51P2WP5AR B2/B3). + case "\$mode" in + ok) printf '%s\n' '{"id":1,"default_branch":"next","full_name":"acme/widgets"}' ;; + fail) echo "curl stub: simulated repo lookup failure" >&2; exit 1 ;; + fail-json) printf '%s\n' '{"default_branch":"next"}'; exit 22 ;; + null) printf '%s\n' '{"default_branch":null}' ;; + numeric) printf '%s\n' '{"default_branch":7}' ;; + blank) printf '%s\n' '{"default_branch":" "}' ;; + *) echo "curl stub: unknown GET mode \$mode" >&2; exit 1 ;; + esac exit 0 fi if [[ "\$url" == */api/v1/repos/acme/widgets/pulls ]]; then @@ -131,6 +136,39 @@ run_pr_create -t "fix thing" assert_rc "rc" 0 "$RC" assert_eq "base field is next, never main" "next" "$(json_field "$ROOT/payload.json" base)" +echo "== (5) B2: HTTP failure with parseable JSON on stdout is a FAILED resolution ==" +: > "$ROOT/curl-calls.log"; rm -f "$ROOT/payload.json" +RC=0 +OUT=$(cd "$ROOT/repo" && env -i \ + PATH="$ROOT/stub:/usr/bin:/bin" \ + HOME="$ROOT/home" \ + GITEA_TOKEN=stub-token \ + CURL_STUB_GET_MODE=fail-json \ + bash "$TOOLS/pr-create.sh" -t "fix thing" 2>"$ROOT/err.txt") +RC=$? +ERR="$(cat "$ROOT/err.txt")" +assert_rc "nonzero rc on HTTP failure despite valid JSON" 1 "$RC" +assert_contains "loud error names -B" "$ERR" "could not resolve the forge default branch" +assert_contains "error names the remedy" "$ERR" "pass -B explicitly" +assert_eq "no POST issued" 0 "$(calls_matching '/pulls$')" + +echo "== (6) B3: null / numeric / blank default_branch are failed resolutions ==" +for bad in null numeric blank; do + : > "$ROOT/curl-calls.log"; rm -f "$ROOT/payload.json" + RC=0 + OUT=$(cd "$ROOT/repo" && env -i \ + PATH="$ROOT/stub:/usr/bin:/bin" \ + HOME="$ROOT/home" \ + GITEA_TOKEN=stub-token \ + CURL_STUB_GET_MODE="$bad" \ + bash "$TOOLS/pr-create.sh" -t "fix thing" 2>"$ROOT/err.txt") + RC=$? + ERR="$(cat "$ROOT/err.txt")" + assert_rc "B3 $bad: nonzero rc" 1 "$RC" + assert_contains "B3 $bad: loud error" "$ERR" "could not resolve the forge default branch" + assert_eq "B3 $bad: no POST issued" 0 "$(calls_matching '/pulls$')" +done + echo echo "pass=$PASS fail=$FAIL" if [ "$FAIL" -gt 0 ]; then diff --git a/packages/mosaic/package.json b/packages/mosaic/package.json index 2c8e873d..cc2a48a6 100644 --- a/packages/mosaic/package.json +++ b/packages/mosaic/package.json @@ -25,7 +25,7 @@ "lint": "eslint src", "typecheck": "tsc --noEmit", "test": "vitest run --passWithNoTests && pnpm run test:framework-shell", - "test:framework-shell": "bash framework/tools/quality/scripts/check-test-enumeration.sh && bash framework/tools/quality/scripts/test-check-test-enumeration.sh && python3 framework/tools/quality/scripts/test-framework-drift-check.py && bash framework/tools/quality/scripts/test-framework-drift-doctor.sh && bash framework/systemd/user/test-fleet-units.sh && python3 src/lease-broker/daemon_deadline_unittest.py && python3 src/lease-broker/normative_fragments_unittest.py && python3 src/lease-broker/promotion_binding_unittest.py && python3 src/lease-broker/promotion_trigger_unittest.py && python3 src/lease-broker/receipt_challenge_unittest.py && python3 src/lease-broker/context_recovery_unittest.py && python3 src/lease-broker/recovery_runtime_unittest.py && python3 src/lease-broker/recovery_b1_adversarial_unittest.py && python3 src/lease-broker/receipt_observer_client_unittest.py && python3 src/lease-broker/invariant_r_unittest.py && python3 src/lease-broker/framework_skill_portability_unittest.py && python3 src/lease-broker/revoke_noop_unittest.py && python3 src/mutator-gate/runtime_tools_unittest.py && python3 src/mutator-gate/runtime_launch_guard_unittest.py && python3 src/mutator-gate/version_coupling_unittest.py && python3 framework/tools/lease-broker/check-runtime-launches.py --root ../.. && bash framework/tools/codex/test-pr-diff-context.sh && bash framework/tools/qa/test-deps-preflight.sh && bash framework/tools/git/test-pr-edit.sh && bash framework/tools/git/test-pr-review-gitea-comment.sh && bash framework/tools/git/test-pr-review-repo-host-override.sh && bash framework/tools/git/test-ci-queue-wait-no-status.sh && bash framework/tools/git/test-ci-queue-wait-branch-absent.sh && bash framework/tools/git/test-ci-queue-wait-tristate.sh && bash framework/tools/git/test-ci-queue-wait-github-checks.sh && bash framework/tools/git/test-ci-queue-wait-no-ci-expected.sh && bash framework/tools/git/test-pr-merge-queue-branch.sh && bash framework/tools/git/test-pr-merge-no-ci-expected.sh && bash framework/tools/git/test-pr-merge-fork-ci-status.sh && bash framework/tools/git/test-pr-merge-head-pin.sh && bash framework/tools/git/test-pr-merge-message-field.sh && bash framework/tools/git/test-git-credential-mosaic.sh && bash framework/tools/git/test-gitea-token-identity.sh && bash framework/tools/git/test-explain-diagnostic-status-neutral.sh && bash framework/tools/git/test-detect-platform-outside-repo.sh && bash framework/tools/woodpecker/test-terminal-green-contract.sh && bash framework/tools/_scripts/test-install-ordering-guard.sh && bash framework/tools/_scripts/test-mosaic-init-rce.sh && bash framework/tools/tmux/agent-send.test.sh && bash framework/tools/wake/test-wake-store-ack.sh && bash framework/tools/wake/test-wake-store-enqueue-race.sh && bash framework/tools/wake/test-wake-digest-hmac.sh && bash framework/tools/wake/test-wake-digest-quarantine.sh && bash framework/tools/wake/test-wake-detector.sh && bash framework/tools/wake/test-wake-fn-oracle.sh && bash framework/tools/wake/test-wake-reconcile.sh && bash framework/tools/wake/test-wake-beacon.sh && bash framework/tools/wake/test-wake-preimage.sh && bash framework/tools/wake/test-wake-install.sh && bash framework/tools/glpi/test-list-http-status.sh && bash framework/tools/orchestrator/test-board-roll.sh && bash framework/tools/woodpecker/test-ci-wait-exit-matrix.sh && bash framework/tools/_scripts/test-fleet-transport-check.sh && bash framework/tools/_scripts/test-brain-home-check.sh && bash framework/tools/fleet/test-agent-session-broker-preflight.sh" + "test:framework-shell": "bash framework/tools/quality/scripts/check-test-enumeration.sh && bash framework/tools/quality/scripts/test-check-test-enumeration.sh && python3 framework/tools/quality/scripts/test-framework-drift-check.py && bash framework/tools/quality/scripts/test-framework-drift-doctor.sh && bash framework/systemd/user/test-fleet-units.sh && python3 src/lease-broker/daemon_deadline_unittest.py && python3 src/lease-broker/normative_fragments_unittest.py && python3 src/lease-broker/promotion_binding_unittest.py && python3 src/lease-broker/promotion_trigger_unittest.py && python3 src/lease-broker/receipt_challenge_unittest.py && python3 src/lease-broker/context_recovery_unittest.py && python3 src/lease-broker/recovery_runtime_unittest.py && python3 src/lease-broker/recovery_b1_adversarial_unittest.py && python3 src/lease-broker/receipt_observer_client_unittest.py && python3 src/lease-broker/invariant_r_unittest.py && python3 src/lease-broker/framework_skill_portability_unittest.py && python3 src/lease-broker/revoke_noop_unittest.py && python3 src/mutator-gate/runtime_tools_unittest.py && python3 src/mutator-gate/runtime_launch_guard_unittest.py && python3 src/mutator-gate/version_coupling_unittest.py && python3 framework/tools/lease-broker/check-runtime-launches.py --root ../.. && bash framework/tools/codex/test-pr-diff-context.sh && bash framework/tools/qa/test-deps-preflight.sh && bash framework/tools/git/test-pr-edit.sh && bash framework/tools/git/test-pr-create-fallback-default-base.sh && bash framework/tools/git/test-pr-review-gitea-comment.sh && bash framework/tools/git/test-pr-review-repo-host-override.sh && bash framework/tools/git/test-ci-queue-wait-no-status.sh && bash framework/tools/git/test-ci-queue-wait-branch-absent.sh && bash framework/tools/git/test-ci-queue-wait-tristate.sh && bash framework/tools/git/test-ci-queue-wait-github-checks.sh && bash framework/tools/git/test-ci-queue-wait-no-ci-expected.sh && bash framework/tools/git/test-pr-merge-queue-branch.sh && bash framework/tools/git/test-pr-merge-no-ci-expected.sh && bash framework/tools/git/test-pr-merge-fork-ci-status.sh && bash framework/tools/git/test-pr-merge-head-pin.sh && bash framework/tools/git/test-pr-merge-message-field.sh && bash framework/tools/git/test-git-credential-mosaic.sh && bash framework/tools/git/test-gitea-token-identity.sh && bash framework/tools/git/test-explain-diagnostic-status-neutral.sh && bash framework/tools/git/test-detect-platform-outside-repo.sh && bash framework/tools/woodpecker/test-terminal-green-contract.sh && bash framework/tools/_scripts/test-install-ordering-guard.sh && bash framework/tools/_scripts/test-mosaic-init-rce.sh && bash framework/tools/tmux/agent-send.test.sh && bash framework/tools/wake/test-wake-store-ack.sh && bash framework/tools/wake/test-wake-store-enqueue-race.sh && bash framework/tools/wake/test-wake-digest-hmac.sh && bash framework/tools/wake/test-wake-digest-quarantine.sh && bash framework/tools/wake/test-wake-detector.sh && bash framework/tools/wake/test-wake-fn-oracle.sh && bash framework/tools/wake/test-wake-reconcile.sh && bash framework/tools/wake/test-wake-beacon.sh && bash framework/tools/wake/test-wake-preimage.sh && bash framework/tools/wake/test-wake-install.sh && bash framework/tools/glpi/test-list-http-status.sh && bash framework/tools/orchestrator/test-board-roll.sh && bash framework/tools/woodpecker/test-ci-wait-exit-matrix.sh && bash framework/tools/_scripts/test-fleet-transport-check.sh && bash framework/tools/_scripts/test-brain-home-check.sh && bash framework/tools/fleet/test-agent-session-broker-preflight.sh" }, "dependencies": { "@mosaicstack/brain": "workspace:*",