mirror of
https://github.com/Sea-Haven-Industries/shoc-pr-review-runner.git
synced 2026-09-30 06:03:14 +00:00
139 lines
5.7 KiB
Bash
139 lines
5.7 KiB
Bash
|
|
#!/usr/bin/env bash
|
||
|
|
# Render /workspace/artifacts/review-evidence.md (spec §18) from the recorded
|
||
|
|
# gate statuses and PR metadata. Every check appears with an explicit status:
|
||
|
|
# PASS / FAIL / NOT_APPLICABLE / NOT_RUN / BLOCKED. Phase-2/3 checks the Phase-1
|
||
|
|
# runner cannot execute are stated NOT_RUN with the reason — never omitted,
|
||
|
|
# never converted into a pass.
|
||
|
|
#
|
||
|
|
# Reads: REVIEW_TYPE, TICKET, REVIEW_NOTES, RUN_ID/GITHUB_RUN_ID, artifacts from
|
||
|
|
# earlier steps ($ARTIFACTS_DIR/{frontend,backend}-pr.json, checkout.json,
|
||
|
|
# gate-status.tsv)
|
||
|
|
|
||
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
|
|
# shellcheck source=lib.sh
|
||
|
|
source "$SCRIPT_DIR/lib.sh"
|
||
|
|
|
||
|
|
require_env REVIEW_TYPE
|
||
|
|
EVIDENCE_FILE="$ARTIFACTS_DIR/review-evidence.md"
|
||
|
|
run_id="${GITHUB_RUN_ID:-local}"
|
||
|
|
|
||
|
|
# The evidence report is only as trustworthy as the gate table it renders.
|
||
|
|
assert_gate_table_intact
|
||
|
|
|
||
|
|
# Untrusted strings (PR titles, branch names, dispatcher-supplied ticket and
|
||
|
|
# notes) must not be able to forge lines inside the evidence report — the agent
|
||
|
|
# is told the evidence report is the only source of truth for what executed.
|
||
|
|
# Control characters and newlines are stripped and the length is capped, so a
|
||
|
|
# crafted value stays on the single line it was rendered into.
|
||
|
|
sanitize() {
|
||
|
|
printf '%s' "$1" | tr -d '\000-\037' | cut -c1-200
|
||
|
|
}
|
||
|
|
|
||
|
|
pr_field() { # pr_field <side> <jq-expr> [fallback]
|
||
|
|
local f="$ARTIFACTS_DIR/$1-pr.json"
|
||
|
|
if [ -f "$f" ]; then sanitize "$(jq -r "$2" "$f")"; else echo "${3:-not in scope}"; fi
|
||
|
|
}
|
||
|
|
|
||
|
|
g() { gate_status "$1"; }
|
||
|
|
|
||
|
|
# A side that has no PR under review has all its gates NOT_APPLICABLE.
|
||
|
|
side_in_scope() { # side_in_scope <frontend|backend>
|
||
|
|
case "$REVIEW_TYPE" in
|
||
|
|
paired) return 0 ;;
|
||
|
|
"$1") return 0 ;;
|
||
|
|
*) return 1 ;;
|
||
|
|
esac
|
||
|
|
}
|
||
|
|
|
||
|
|
fe_gate() { if side_in_scope frontend; then g "$1"; else echo "NOT_APPLICABLE (backend-only review)"; fi; }
|
||
|
|
be_gate() { if side_in_scope backend; then g "$1"; else echo "NOT_APPLICABLE (frontend-only review)"; fi; }
|
||
|
|
|
||
|
|
companion_note=""
|
||
|
|
if [ "$REVIEW_TYPE" != "paired" ] && [ -f "$ARTIFACTS_DIR/checkout.json" ]; then
|
||
|
|
cb="$(jq -r '.companion_branch' "$ARTIFACTS_DIR/checkout.json")"
|
||
|
|
companion_note=" (companion checked out at \`$cb\` head for contract context, not under review)"
|
||
|
|
fi
|
||
|
|
|
||
|
|
cat >"$EVIDENCE_FILE" <<EOF
|
||
|
|
# Review Evidence
|
||
|
|
|
||
|
|
## Review Request
|
||
|
|
- Review type: $REVIEW_TYPE
|
||
|
|
- Frontend PR: $(pr_field frontend '"#\(.pr)"') (title and branch names appear in the untrusted section, not here)
|
||
|
|
- Backend PR: $(pr_field backend '"#\(.pr)"')
|
||
|
|
- Ticket: $(sanitize "${TICKET:-not provided}")
|
||
|
|
- Reviewer notes: $(sanitize "${REVIEW_NOTES:-none}")
|
||
|
|
- Workflow run: $run_id
|
||
|
|
|
||
|
|
## Exact Heads
|
||
|
|
- Frontend SHA: $(pr_field frontend '.short_sha')$( side_in_scope frontend || printf '%s' "$companion_note")
|
||
|
|
- Backend SHA: $(pr_field backend '.short_sha')$( side_in_scope backend || printf '%s' "$companion_note")
|
||
|
|
- Frontend base: $(pr_field frontend '.base_ref')
|
||
|
|
- Backend base: $(pr_field backend '.base_ref')
|
||
|
|
|
||
|
|
## Governance Signals
|
||
|
|
- Frontend PR CI status: $(pr_field frontend '.ci_status')
|
||
|
|
- Backend PR CI status: $(pr_field backend '.ci_status')
|
||
|
|
- Frontend PR mergeable: $(pr_field frontend '.mergeable // "unknown"')
|
||
|
|
- Backend PR mergeable: $(pr_field backend '.mergeable // "unknown"')
|
||
|
|
- CI statuses come from the PR head's check-runs; red/pending/missing is a governance signal, not silently omitted.
|
||
|
|
|
||
|
|
## Stack Status
|
||
|
|
- Frontend parent: NOT_RUN (stacked-PR resolution is Phase 4)
|
||
|
|
- Backend parent: NOT_RUN (stacked-PR resolution is Phase 4)
|
||
|
|
- Base integrity: NOT_RUN (base comparison is Phase 4)
|
||
|
|
|
||
|
|
## Backend Gates
|
||
|
|
- Restore: $(be_gate backend.restore)
|
||
|
|
- Release build: $(be_gate backend.build)
|
||
|
|
- Tests: $(be_gate backend.test)
|
||
|
|
- Migration list: NOT_RUN (database provisioning is Phase 2)
|
||
|
|
- Migration script: NOT_RUN (database provisioning is Phase 2)
|
||
|
|
- Migration apply: NOT_RUN (database provisioning is Phase 2)
|
||
|
|
- Startup: NOT_RUN (runtime environment is Phase 2)
|
||
|
|
- Health endpoint: NOT_RUN (runtime environment is Phase 2)
|
||
|
|
- API runtime scenarios: NOT_RUN (runtime environment is Phase 2)
|
||
|
|
|
||
|
|
## Frontend Gates
|
||
|
|
- Clean install: $(fe_gate frontend.install)
|
||
|
|
- Lint: $(fe_gate frontend.lint)
|
||
|
|
- TypeScript + production build: $(fe_gate frontend.build) (tsc -b runs inside the build script)
|
||
|
|
- Unit/component tests: $(fe_gate frontend.unit_tests)
|
||
|
|
- Development startup: NOT_RUN (manual route exercise is Phase 2)
|
||
|
|
- Production preview: NOT_RUN (runtime environment is Phase 2)
|
||
|
|
|
||
|
|
## Browser Validation
|
||
|
|
- Mocked Playwright: $(fe_gate frontend.e2e_mocked)
|
||
|
|
- Live Playwright: NOT_RUN (live backend integration is Phase 3; MOCKED COVERAGE IS NOT LIVE COVERAGE)
|
||
|
|
- Affected routes: NOT_RUN (live browser validation is Phase 3)
|
||
|
|
- Console errors: NOT_RUN (live browser validation is Phase 3)
|
||
|
|
- Failed requests: NOT_RUN (live browser validation is Phase 3)
|
||
|
|
|
||
|
|
## Runtime Limitations
|
||
|
|
- The Phase 1 runner does not provision a database, start either application, or run live browser flows. Any conclusion about runtime behavior must come from code inspection and is not runtime-verified.
|
||
|
|
- Disabled integrations: all (no runtime environment in Phase 1)
|
||
|
|
- Mocked external systems: the Playwright suite mocks ALL backend API calls via page.route
|
||
|
|
- Unexecuted checks: listed NOT_RUN above with reasons
|
||
|
|
|
||
|
|
## Logs and Artifacts
|
||
|
|
$(if [ -d "$LOG_DIR" ] && [ -n "$(ls -A "$LOG_DIR" 2>/dev/null)" ]; then
|
||
|
|
for f in "$LOG_DIR"/*; do
|
||
|
|
printf -- '- logs/%s\n' "$(basename "$f")"
|
||
|
|
done
|
||
|
|
else
|
||
|
|
printf -- '- none\n'
|
||
|
|
fi)
|
||
|
|
|
||
|
|
## Gate Detail
|
||
|
|
$(if [ -f "$GATE_STATUS_FILE" ]; then
|
||
|
|
while IFS=$'\t' read -r key status det; do
|
||
|
|
# shellcheck disable=SC2016 # backticks are literal markdown
|
||
|
|
printf -- '- `%s`: %s%s\n' "$key" "$status" "${det:+ — $det}"
|
||
|
|
done <"$GATE_STATUS_FILE"
|
||
|
|
else
|
||
|
|
printf -- '- no gates recorded\n'
|
||
|
|
fi)
|
||
|
|
EOF
|
||
|
|
|
||
|
|
log "evidence written to $EVIDENCE_FILE"
|