Files
gstack/bin/gstack-review-read
T
Garry Tanandcapy-ai[bot] b1f78301db The lander must accept each owner-approved gate change before a PR merges (GST-2491) (#4304)
* Let an owner recorded approval close a gate finding in unattended ship (GST-2491)

An unattended /ship logs every gate finding open and nothing could close it, so a PR with an owner-approved gate change could never finish. gstack-approvals gate records the owner disposition outside the run; it closes only that fingerprint on a review row whose range holds the head, and finish, the receipt, the PR body and READY all carry it.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Keep the pinned review brief unchanged; document the resume steps for a dispositioned gate finding (GST-2491)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Bind a gate disposition to its gate change and show it as kept to the rest of /ship (GST-2491)

A disposition now names the finding's gate_id as well as its fingerprint, so a later loosening of the same line stays open. gstack-review-read shows a closed gate finding as kept with the disposition's approval id, so the existing --blocking-open, --gate-open, draft and Gate edits rules carry it without prose changes, and an unreadable approval record applies no disposition instead of failing the reader.

Review-Finding: p1/red-team#1 reader=red-team
Review-Finding: s11/outside-voice#1 reader=outside-voice
Review-Finding: s11/outside-voice#2 reader=outside-voice
Review-Finding: s11/adversarial-native#1 reader=adversarial-native
Review-Finding: s11/adversarial-native#2 reader=adversarial-native
Review-Finding: s11/adversarial-native#4 reader=adversarial-native
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* List the Gate disposition line in the receipt and prose troubleshooting entries (GST-2491)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Require the lander to accept each owner gate disposition before merging (GST-2491)

Land and a new read-only verify --pin re-derive the dispositions the approval store applies at the pin and merge only with --accept-gate-disposition for each one. The gate writer refuses while any worktree holds an open /ship run on the branch, under a lock; a row counts only in the writer's full shape with its digest; receipt write needs --body when a disposition applies; ready and land cross-check the receipt and body against the store. Supersedes #4299.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Regenerate the result-code anchors for the gate-disposition refusal (GST-2491)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Always record the receipt's gate dispositions and refuse a store that disagrees at merge (GST-2491)

The receipt always carries gate_dispositions (an empty list when none apply) and write refuses on an unreadable store or a git error. Land, verify and ready refuse a receipt id the merge-time store lacks, naming the likely wrong state root or mirror, and a field-less receipt beside an applied row. A git ancestry error while deriving dispositions refuses instead of reading as not applied.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Count a disposition whose head a rewrite removed as not applied, and expect the receipt to end with gate_dispositions (GST-2491)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Trim and check the gate disposition recorder name, and let a parked run's ended lease stop blocking it (GST-2491)

Review-Finding: s11/outside-voice#3 reader=outside-voice
Review-Finding: s11/outside-voice#2 reader=outside-voice
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-11-12 01:49:08 +00:00

59 lines
3.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# gstack-review-read — read review log and config for dashboard
# Usage: gstack-review-read
#
# Emits, in order: reviews JSONL with computed review_freshness, ---CONFIG--- (skip_eng_review),
# ---HEAD--- (short sha), ---WTREE--- (current working-tree fingerprint from
# bin/gstack-wtree --review, or "unknown"), ---TREE--- (HEAD tree, informational) and
# ---DIRTY--- (tracked-file dirty flag). Consumers grade diff-scoped review
# rows using review_freshness; legacy or uncaptured diff evidence cannot fall
# back to HEAD equality. Plan rows are unchanged. A gate finding the owner's gstack-approvals
# gate row closes reads `kept` with its `disposition`, and the row's `gate_dispositions` lists
# those approval ids (lib/gate-dispositions.ts); values the row itself carried are discarded.
set -euo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) if command -v cygpath >/dev/null 2>&1; then GSTACK_BUN_CONFIG=NUL; fi ;; esac
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
case "$(uname -s)" in
MINGW*|MSYS*|CYGWIN*) command -v cygpath >/dev/null 2>&1 && SCRIPT_DIR="$(cygpath -m "$SCRIPT_DIR")" ;;
esac
eval "$("$SCRIPT_DIR/gstack-slug" 2>/dev/null)"
SLUG=$("$SCRIPT_DIR/gstack-slug" --get REPO_SLUG 2>/dev/null || true)
. "$(dirname "$0")/gstack-state-root.sh" 2>/dev/null || { echo "$0: cannot resolve the gstack state root: $(dirname "$0")/gstack-state-root.sh is missing. fix: reinstall with ./setup or /gstack-upgrade (docs/state-root.md)" >&2; exit 1; }
gstack_state_root_select
GSTACK_HOME="$_gstack_sr_root"
WTREE=$("$SCRIPT_DIR/gstack-wtree" --review 2>/dev/null || echo "unknown")
if [ -f "$GSTACK_HOME/projects/$SLUG/$BRANCH-reviews.jsonl" ]; then
GSTACK_REVIEW_LIB="$SCRIPT_DIR/../lib/review-evidence.ts" GSTACK_GATE_LIB="$SCRIPT_DIR/../lib/gate-dispositions.ts" GSTACK_REVIEW_WTREE="$WTREE" bun --no-env-file --config="${GSTACK_BUN_CONFIG:-/dev/null}" -e '
const { reviewFreshness } = await import(process.env.GSTACK_REVIEW_LIB);
const { dispositionedOn, gateKey, readGateDispositions, showDispositioned } = await import(process.env.GSTACK_GATE_LIB);
let dispositions = [];
try { dispositions = readGateDispositions(process.cwd()); }
catch (e) { console.error("gstack-review-read: gate dispositions unreadable, none applied: " + e.message); }
for (const line of (await Bun.stdin.text()).split("\n").filter(Boolean)) {
try {
const rec = JSON.parse(line);
delete rec.review_freshness;
delete rec.gate_dispositions;
for (const f of Array.isArray(rec.findings) ? rec.findings : []) if (f && typeof f === "object") delete f.disposition;
const closed = dispositionedOn(process.cwd(), rec, dispositions);
const freshness = reviewFreshness(rec, process.env.GSTACK_REVIEW_WTREE, new Set(), new Set(closed.map(d => gateKey(d.finding, d.gate_id))));
if (freshness) rec.review_freshness = freshness;
showDispositioned(rec, closed);
console.log(JSON.stringify(rec));
} catch { console.error("gstack-review-read: skipping malformed review row"); }
}
' < "$GSTACK_HOME/projects/$SLUG/$BRANCH-reviews.jsonl"
else
echo "NO_REVIEWS"
fi
echo "---CONFIG---"
"$SCRIPT_DIR/gstack-config" get skip_eng_review 2>/dev/null || echo "false"
echo "---HEAD---"
git rev-parse --short HEAD 2>/dev/null || echo "unknown"
echo "---WTREE---"
echo "$WTREE"
echo "---TREE---"
git rev-parse 'HEAD^{tree}' 2>/dev/null || echo "unknown"
echo "---DIRTY---"
if [ -n "$(git status --porcelain -uno 2>/dev/null | head -1)" ]; then echo "true"; else echo "false"; fi