Compare commits
2 Commits
feat/869-c
...
fix/ci-que
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23cbdaf8df | ||
|
|
cd67042184 |
@@ -185,6 +185,16 @@ switch ($platform) {
|
|||||||
$headSha = ($branchPayload.commit.id | Out-String).Trim()
|
$headSha = ($branchPayload.commit.id | Out-String).Trim()
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
|
# A not-yet-pushed feature branch has no in-flight pipeline, so the
|
||||||
|
# pre-push queue guard must treat 404 as "queue clear", not crash.
|
||||||
|
$statusCode = $null
|
||||||
|
if ($_.Exception.Response) {
|
||||||
|
$statusCode = [int]$_.Exception.Response.StatusCode
|
||||||
|
}
|
||||||
|
if ($statusCode -eq 404) {
|
||||||
|
Write-Host "[ci-queue-wait] branch $Branch not yet on remote — no in-flight pipeline; queue clear."
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
Write-Error "Could not resolve $Branch head SHA from Gitea API."
|
Write-Error "Could not resolve $Branch head SHA from Gitea API."
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,7 +137,21 @@ gitea_get_branch_head_sha() {
|
|||||||
local branch="$3"
|
local branch="$3"
|
||||||
local token="$4"
|
local token="$4"
|
||||||
local url="https://${host}/api/v1/repos/${repo}/branches/${branch}"
|
local url="https://${host}/api/v1/repos/${repo}/branches/${branch}"
|
||||||
curl -fsSL -H "User-Agent: curl/8" -H "Authorization: token ${token}" "$url" | python3 -c '
|
# Capture HTTP status so an absent branch (404) is distinguished from an API
|
||||||
|
# error. A not-yet-pushed feature branch has no in-flight pipeline, so the
|
||||||
|
# pre-push queue guard must treat 404 as "queue clear", not crash.
|
||||||
|
local resp code body
|
||||||
|
resp=$(curl -sS -H "User-Agent: curl/8" -H "Authorization: token ${token}" -w $'\n%{http_code}' "$url")
|
||||||
|
code="${resp##*$'\n'}"
|
||||||
|
body="${resp%$'\n'*}"
|
||||||
|
if [[ "$code" == "404" ]]; then
|
||||||
|
echo "__BRANCH_ABSENT__"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ "$code" != "200" ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
printf '%s' "$body" | python3 -c '
|
||||||
import json, sys
|
import json, sys
|
||||||
data = json.load(sys.stdin)
|
data = json.load(sys.stdin)
|
||||||
commit = data.get("commit") or {}
|
commit = data.get("commit") or {}
|
||||||
@@ -219,6 +233,10 @@ elif [[ "$PLATFORM" == "gitea" ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
HEAD_SHA=$(gitea_get_branch_head_sha "$HOST" "$OWNER/$REPO" "$BRANCH" "$TOKEN")
|
HEAD_SHA=$(gitea_get_branch_head_sha "$HOST" "$OWNER/$REPO" "$BRANCH" "$TOKEN")
|
||||||
|
if [[ "$HEAD_SHA" == "__BRANCH_ABSENT__" ]]; then
|
||||||
|
echo "[ci-queue-wait] branch ${BRANCH} not yet on remote — no in-flight pipeline; queue clear."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
if [[ -z "$HEAD_SHA" ]]; then
|
if [[ -z "$HEAD_SHA" ]]; then
|
||||||
echo "Error: Could not resolve ${BRANCH} head SHA." >&2
|
echo "Error: Could not resolve ${BRANCH} head SHA." >&2
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Regression harness for ci-queue-wait.sh's 404-branch-absent handling.
|
||||||
|
#
|
||||||
|
# gitea_get_branch_head_sha() resolves a branch's head SHA before the
|
||||||
|
# pre-push queue guard runs. A branch that has never been pushed doesn't
|
||||||
|
# exist on the remote yet, so Gitea's branches/<branch> endpoint 404s.
|
||||||
|
# Before the fix, `curl -fsSL` failed on the 404, its empty stdout was piped
|
||||||
|
# into `python3 -c 'json.load(sys.stdin)'`, and the resulting
|
||||||
|
# JSONDecodeError crashed the guard -- blocking every new feature branch's
|
||||||
|
# first push. The fix must treat 404 as "no in-flight pipeline" (queue
|
||||||
|
# clear) while still failing closed on a genuine API error.
|
||||||
|
#
|
||||||
|
# Covers:
|
||||||
|
# (a) 404 branch-absent -> exit 0, "queue clear" message.
|
||||||
|
# (b) 200 existing branch + a terminal CI state -> unchanged behavior.
|
||||||
|
# (c) genuine API error (500) -> still fail-closed (nonzero exit).
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
WORK_DIR="${MOSAIC_TEST_WORK_DIR:-$PWD/.mosaic-test-work/ci-queue-wait-branch-absent}"
|
||||||
|
REPO_DIR="$WORK_DIR/repo"
|
||||||
|
STUB_DIR="$WORK_DIR/stubs"
|
||||||
|
|
||||||
|
rm -rf "$WORK_DIR"
|
||||||
|
mkdir -p "$REPO_DIR" "$STUB_DIR"
|
||||||
|
|
||||||
|
git -C "$REPO_DIR" init -q
|
||||||
|
git -C "$REPO_DIR" remote add origin https://git.example.test/acme/widgets.git
|
||||||
|
|
||||||
|
# Minimal curl stub. Selects a canned response by inspecting which Gitea
|
||||||
|
# endpoint is being hit (branches/<branch> vs commits/<sha>/status) and
|
||||||
|
# whether -w '%{http_code}' was requested. Only the patched branch-lookup
|
||||||
|
# call passes -w; the unpatched call and the (unchanged) status call both
|
||||||
|
# use plain `curl -fsSL` semantics -- exit nonzero and print nothing on a
|
||||||
|
# non-2xx response. This lets the same stub exercise both the pre-fix and
|
||||||
|
# post-fix branch-lookup code paths faithfully.
|
||||||
|
cat > "$STUB_DIR/curl" <<'SH'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
has_w=0
|
||||||
|
url=""
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
-w) has_w=1 ;;
|
||||||
|
http://*|https://*) url="$arg" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
case "$url" in
|
||||||
|
*/branches/*) mode="${MOSAIC_STUB_BRANCH_MODE:?MOSAIC_STUB_BRANCH_MODE not set}" ;;
|
||||||
|
*/status) mode="${MOSAIC_STUB_STATUS_MODE:-terminal-success}" ;;
|
||||||
|
*)
|
||||||
|
echo "curl stub: unrecognized URL: $url" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$mode" in
|
||||||
|
404) code=404; body="" ;;
|
||||||
|
200) code=200; body='{"commit":{"id":"deadbeefcafef00d0123456789abcdef01234567"}}' ;;
|
||||||
|
500) code=500; body='{"message":"internal server error"}' ;;
|
||||||
|
no-status) code=200; body='{}' ;;
|
||||||
|
terminal-success) code=200; body='{"state":"success"}' ;;
|
||||||
|
*)
|
||||||
|
echo "curl stub: unknown mode=$mode" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ "$has_w" == 1 ]]; then
|
||||||
|
printf '%s\n%s' "$body" "$code"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Unpatched branch-lookup call / status-endpoint call: real curl -fsSL
|
||||||
|
# exits nonzero and emits nothing on stdout for a non-2xx response.
|
||||||
|
if [[ "$code" != "200" ]]; then
|
||||||
|
exit 22
|
||||||
|
fi
|
||||||
|
printf '%s' "$body"
|
||||||
|
SH
|
||||||
|
chmod +x "$STUB_DIR/curl"
|
||||||
|
|
||||||
|
run_ci_queue_wait() {
|
||||||
|
local branch="$1"
|
||||||
|
(
|
||||||
|
cd "$REPO_DIR"
|
||||||
|
export PATH="$STUB_DIR:$PATH"
|
||||||
|
export MOSAIC_CREDENTIALS_FILE="$WORK_DIR/no-credentials.json"
|
||||||
|
export GITEA_TOKEN="stub-token"
|
||||||
|
export GITEA_URL="https://git.example.test"
|
||||||
|
"$SCRIPT_DIR/ci-queue-wait.sh" -B "$branch" --purpose push -t 5 -i 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fail=0
|
||||||
|
|
||||||
|
# (a) 404 branch-absent -> queue clear, exit 0.
|
||||||
|
set +e
|
||||||
|
out_a=$(MOSAIC_STUB_BRANCH_MODE=404 run_ci_queue_wait "feat/not-pushed-yet" 2>&1)
|
||||||
|
status_a=$?
|
||||||
|
set -e
|
||||||
|
if [[ "$status_a" -ne 0 ]]; then
|
||||||
|
echo "FAIL(a): expected exit 0 for 404 branch-absent, got $status_a" >&2
|
||||||
|
echo "$out_a" >&2
|
||||||
|
fail=1
|
||||||
|
elif [[ "$out_a" != *"queue clear"* ]]; then
|
||||||
|
echo "FAIL(a): expected a queue-clear message, got:" >&2
|
||||||
|
echo "$out_a" >&2
|
||||||
|
fail=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (b) 200 existing branch + terminal CI state -> unchanged behavior, exit 0.
|
||||||
|
set +e
|
||||||
|
out_b=$(MOSAIC_STUB_BRANCH_MODE=200 MOSAIC_STUB_STATUS_MODE=terminal-success run_ci_queue_wait "main" 2>&1)
|
||||||
|
status_b=$?
|
||||||
|
set -e
|
||||||
|
if [[ "$status_b" -ne 0 ]]; then
|
||||||
|
echo "FAIL(b): expected exit 0 for existing branch with terminal status, got $status_b" >&2
|
||||||
|
echo "$out_b" >&2
|
||||||
|
fail=1
|
||||||
|
elif [[ "$out_b" != *"sha=deadbeefcafef00d0123456789abcdef01234567"* ]]; then
|
||||||
|
echo "FAIL(b): expected the resolved HEAD SHA to be logged, got:" >&2
|
||||||
|
echo "$out_b" >&2
|
||||||
|
fail=1
|
||||||
|
elif [[ "$out_b" == *"queue clear"* ]]; then
|
||||||
|
echo "FAIL(b): an existing branch must not take the branch-absent path" >&2
|
||||||
|
echo "$out_b" >&2
|
||||||
|
fail=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (c) genuine API error (500) -> still fail-closed, exit nonzero.
|
||||||
|
set +e
|
||||||
|
out_c=$(MOSAIC_STUB_BRANCH_MODE=500 run_ci_queue_wait "feat/some-branch" 2>&1)
|
||||||
|
status_c=$?
|
||||||
|
set -e
|
||||||
|
if [[ "$status_c" -eq 0 ]]; then
|
||||||
|
echo "FAIL(c): expected a nonzero exit for a genuine 500 API error, got 0" >&2
|
||||||
|
echo "$out_c" >&2
|
||||||
|
fail=1
|
||||||
|
elif [[ "$out_c" == *"queue clear"* ]]; then
|
||||||
|
echo "FAIL(c): a genuine API error must not be reported as queue-clear" >&2
|
||||||
|
echo "$out_c" >&2
|
||||||
|
fail=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$fail" -eq 0 ]]; then
|
||||||
|
echo "ci-queue-wait branch-absent regression passed (3/3 cases)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit "$fail"
|
||||||
@@ -4,18 +4,18 @@ Helper scripts for r0 coordinator / orchestrator sessions — mission lifecycle,
|
|||||||
session health, continuation, and board maintenance. See
|
session health, continuation, and board maintenance. See
|
||||||
`framework/guides/ORCHESTRATOR-PROTOCOL.md` for the surrounding process.
|
`framework/guides/ORCHESTRATOR-PROTOCOL.md` for the surrounding process.
|
||||||
|
|
||||||
| Script | Purpose |
|
| Script | Purpose |
|
||||||
|--------|---------|
|
| -------------------- | ----------------------------------------------------------------------------------------------- |
|
||||||
| `mission-init.sh` | Initialize a new orchestration mission (manifest, scratchpad, TASKS.md). |
|
| `mission-init.sh` | Initialize a new orchestration mission (manifest, scratchpad, TASKS.md). |
|
||||||
| `mission-status.sh` | Show the mission progress dashboard. |
|
| `mission-status.sh` | Show the mission progress dashboard. |
|
||||||
| `session-run.sh` | Generate continuation context and launch the target runtime. |
|
| `session-run.sh` | Generate continuation context and launch the target runtime. |
|
||||||
| `session-resume.sh` | Crash recovery for dead orchestrator sessions. |
|
| `session-resume.sh` | Crash recovery for dead orchestrator sessions. |
|
||||||
| `session-status.sh` | Check agent session health. |
|
| `session-status.sh` | Check agent session health. |
|
||||||
| `continue-prompt.sh` | Generate the continuation prompt for the next session. |
|
| `continue-prompt.sh` | Generate the continuation prompt for the next session. |
|
||||||
| `board-roll.sh` | Keep a LIVE orchestration board under its byte cap by rolling the oldest entries to its LEDGER. |
|
| `board-roll.sh` | Keep a LIVE orchestration board under its byte cap by rolling the oldest entries to its LEDGER. |
|
||||||
| `smoke-test.sh` | Behavior smoke checks for the coord continue/run workflows. |
|
| `smoke-test.sh` | Behavior smoke checks for the coord continue/run workflows. |
|
||||||
| `test-board-roll.sh` | Regression harness for `board-roll.sh`. |
|
| `test-board-roll.sh` | Regression harness for `board-roll.sh`. |
|
||||||
| `_lib.sh` | Shared functions sourced by the above (state files, TASKS.md parsing, locks). |
|
| `_lib.sh` | Shared functions sourced by the above (state files, TASKS.md parsing, locks). |
|
||||||
|
|
||||||
## board-roll.sh
|
## board-roll.sh
|
||||||
|
|
||||||
@@ -35,16 +35,23 @@ always-current `##` sections) is pinned and never touched:
|
|||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
# MOS ORCHESTRATION BOARD — LIVE state
|
# MOS ORCHESTRATION BOARD — LIVE state
|
||||||
|
|
||||||
> protocol blockquote … (pinned)
|
> protocol blockquote … (pinned)
|
||||||
|
|
||||||
## 🟦 Curated always-current section (pinned)
|
## 🟦 Curated always-current section (pinned)
|
||||||
|
|
||||||
…
|
…
|
||||||
|
|
||||||
<!-- BOARD-ROLL:START -->
|
<!-- BOARD-ROLL:START -->
|
||||||
|
|
||||||
### 2026-07-22 (mid²²) — newest tick, stays longest
|
### 2026-07-22 (mid²²) — newest tick, stays longest
|
||||||
|
|
||||||
…
|
…
|
||||||
|
|
||||||
### 2026-07-20 (dawn) — oldest tick, rolled first
|
### 2026-07-20 (dawn) — oldest tick, rolled first
|
||||||
|
|
||||||
…
|
…
|
||||||
|
|
||||||
<!-- BOARD-ROLL:END -->
|
<!-- BOARD-ROLL:END -->
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
"lint": "eslint src",
|
"lint": "eslint src",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"test": "vitest run --passWithNoTests && pnpm run test:framework-shell",
|
"test": "vitest run --passWithNoTests && pnpm run test:framework-shell",
|
||||||
"test:framework-shell": "python3 src/lease-broker/daemon_deadline_unittest.py && python3 src/lease-broker/normative_fragments_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/framework_skill_portability_unittest.py && python3 src/mutator-gate/runtime_tools_unittest.py && python3 src/mutator-gate/runtime_launch_guard_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-review-gitea-comment.sh"
|
"test:framework-shell": "python3 src/lease-broker/daemon_deadline_unittest.py && python3 src/lease-broker/normative_fragments_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/framework_skill_portability_unittest.py && python3 src/mutator-gate/runtime_tools_unittest.py && python3 src/mutator-gate/runtime_launch_guard_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-review-gitea-comment.sh && bash framework/tools/git/test-ci-queue-wait-branch-absent.sh"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mosaicstack/brain": "workspace:*",
|
"@mosaicstack/brain": "workspace:*",
|
||||||
|
|||||||
Reference in New Issue
Block a user