mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 22:14:25 +01:00
fix(wrapper): GSTACK_CAPY_MIN_WRAPPER back to 1; an active wrapper below the minimum is a WARN; gstack-upgrade never leaves a pending marker for the wrapper row and maps every installer stop to a trailer (integration items 2, 9)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
5630ea3c34
commit
d0afbe9c4d
5 files changed
+83
-16
No files matched your search
@@ -432,7 +432,8 @@ _dc_wrapper_rev() {
|
||||
|
||||
# _dc_check_wrapper — the Capy skill copy. The active wrapper's provenance
|
||||
# (GSTACK_CAPY_WRAPPER_REV/PATH, exported by the bootstrapper) decides: current
|
||||
# passes, and a stale Drive copy elsewhere is a WARN for the owner. Without
|
||||
# passes; an active copy below the minimum, or a stale Drive copy elsewhere, is
|
||||
# a WARN for the owner (an older copy still works, capy/support.env). Without
|
||||
# provenance (a direct doctor run, or a wrapper too old to export it) the Drive
|
||||
# copies decide: GSTACK_CAPY_WRAPPER_PATH when it names one, else every
|
||||
# ~/.capy/drive/*/skills/gstack/scripts/install.sh, failing only when each is
|
||||
@@ -455,7 +456,7 @@ _dc_check_wrapper() {
|
||||
fi
|
||||
if [ -n "${GSTACK_CAPY_WRAPPER_REV:-}" ]; then
|
||||
if [ "$GSTACK_CAPY_WRAPPER_REV" -lt "$min" ] 2>/dev/null; then
|
||||
_dc_row FAIL wrapper "the active Capy skill copy ${active:-<unknown>} is wrapper $GSTACK_CAPY_WRAPPER_REV, minimum $min" "$workaround; owner: $owner" CAPY_WRAPPER_STALE
|
||||
_dc_row WARN wrapper "the active Capy skill copy ${active:-<unknown>} is wrapper $GSTACK_CAPY_WRAPPER_REV, minimum $min (it still works)" "$owner" CAPY_WRAPPER_STALE
|
||||
elif [ -n "$stale" ]; then
|
||||
_dc_row WARN wrapper "active wrapper $GSTACK_CAPY_WRAPPER_REV (${active:-unknown path}) is current; stale Drive copies (minimum $min): $stale" "$owner" CAPY_WRAPPER_STALE
|
||||
else
|
||||
|
||||
+39
-9
@@ -77,6 +77,23 @@ REMOTE_VER=$(git -C "$ROOT" show FETCH_HEAD:VERSION 2>/dev/null | head -1)
|
||||
PENDING="$(git -C "$ROOT" rev-parse --absolute-git-dir)/gstack-upgrade.pending"
|
||||
CAPY=0
|
||||
[ -f "$(git -C "$ROOT" rev-parse --absolute-git-dir)/gstack-capy-install.state" ] && [ -x "$ROOT/bin/gstack-capy-install" ] && CAPY=1
|
||||
# Run outside a Capy session, the installer this script starts is the
|
||||
# checkout's own; say so, so the doctor's wrapper row judges that copy.
|
||||
if [ "$CAPY" -eq 1 ] && [ -z "${GSTACK_CAPY_WRAPPER_REV:-}" ] && [ -f "$ROOT/capy/skill/gstack/scripts/install.sh" ]; then
|
||||
GSTACK_CAPY_WRAPPER_REV=$(sed -n 's/^GSTACK_CAPY_WRAPPER=\([0-9][0-9]*\).*/\1/p' "$ROOT/capy/skill/gstack/scripts/install.sh" | head -1)
|
||||
[ -n "$GSTACK_CAPY_WRAPPER_REV" ] && export GSTACK_CAPY_WRAPPER_REV GSTACK_CAPY_WRAPPER_PATH="$ROOT/capy/skill/gstack/scripts/install.sh"
|
||||
fi
|
||||
# stop_trailer ERRFILE — an installer stop line (exit 3, before ./setup) as a
|
||||
# trailer: a RUNTIME_PIN_CONFLICT is `gstack: fail pins`, any other stop
|
||||
# `gstack: fail runtime`.
|
||||
stop_trailer() {
|
||||
_stop=$(grep -E '^gstack-capy-install: .*\([A-Z][A-Z_]*;' "$1" | tail -1)
|
||||
case "$_stop" in
|
||||
'') ;;
|
||||
*'(RUNTIME_PIN_CONFLICT;'*) echo "gstack: fail pins — ${_stop#gstack-capy-install: }" ;;
|
||||
*) echo "gstack: fail runtime — ${_stop#gstack-capy-install: }" ;;
|
||||
esac
|
||||
}
|
||||
# certify — the caller's project (see the header); exits with its outcome.
|
||||
certify() {
|
||||
[ -n "$CALLER_PROJECT" ] && [ "$REINSTALL" -eq 1 ] || exit 0
|
||||
@@ -84,10 +101,10 @@ certify() {
|
||||
_err=$(mktemp "${TMPDIR:-/tmp}/gstack-upgrade.XXXXXX")
|
||||
(cd "$CALLER" && "$ROOT/bin/gstack-capy-install" --project "$CALLER_PROJECT" </dev/null 2>"$_err")
|
||||
_rc=$?
|
||||
_conflict=$(grep '(RUNTIME_PIN_CONFLICT;' "$_err" | tail -1)
|
||||
cat "$_err" >&2; rm -f "$_err"
|
||||
[ "$_rc" -eq 0 ] && exit 0
|
||||
[ -n "$_conflict" ] && echo "gstack: fail pins — ${_conflict#gstack-capy-install: }"
|
||||
cat "$_err" >&2
|
||||
[ "$_rc" -eq 0 ] && { rm -f "$_err"; exit 0; }
|
||||
[ "$_rc" -eq 3 ] && stop_trailer "$_err"
|
||||
rm -f "$_err"
|
||||
exit 1
|
||||
fi
|
||||
(cd "$CALLER" && "$ROOT/bin/gstack-doctor" --check --require runtime,pins --project "$CALLER_PROJECT" </dev/null) && exit 0
|
||||
@@ -97,19 +114,32 @@ reinstall() { # FROM — run the install step; a pending marker survives a failu
|
||||
# With a project to certify next, the installer's own trailer is held back
|
||||
# (printed only when the reinstall fails), so no ok precedes a pins failure.
|
||||
printf '%s\n' "$1" > "$PENDING"
|
||||
_held=$(mktemp "${TMPDIR:-/tmp}/gstack-upgrade.XXXXXX")
|
||||
_held=$(mktemp "${TMPDIR:-/tmp}/gstack-upgrade.XXXXXX") _ierr=$(mktemp "${TMPDIR:-/tmp}/gstack-upgrade.XXXXXX")
|
||||
if [ "$CAPY" -eq 1 ]; then
|
||||
(cd "$ROOT" && GSTACK_CAPY_UPGRADED_FROM="$1" "$ROOT/bin/gstack-capy-install" </dev/null) | while IFS= read -r _line; do
|
||||
case "$_line" in "gstack: "*) [ -n "$CALLER_PROJECT" ] && { printf '%s\n' "$_line" > "$_held"; continue; } ;; esac
|
||||
(cd "$ROOT" && GSTACK_CAPY_UPGRADED_FROM="$1" "$ROOT/bin/gstack-capy-install" </dev/null 2>"$_ierr") | while IFS= read -r _line; do
|
||||
case "$_line" in "gstack: "*) printf '%s\n' "$_line" > "$_held"; continue ;; esac
|
||||
printf '%s\n' "$_line"
|
||||
done
|
||||
_rc=${PIPESTATUS[0]}
|
||||
cat "$_ierr" >&2
|
||||
else
|
||||
(cd "$ROOT" && ./setup --refresh-registered </dev/null)
|
||||
_rc=$?
|
||||
fi
|
||||
if [ "$_rc" -eq 0 ]; then rm -f "$PENDING" "$_held"; certify; fi
|
||||
cat "$_held"; rm -f "$_held"
|
||||
# The wrapper row is an owner action on the Drive copy, never a reinstall
|
||||
# failure: no pending marker for it.
|
||||
if [ "$_rc" -ne 0 ] && [ "$(cat "$_held")" = "gstack: fail wrapper" ]; then
|
||||
rm -f "$PENDING"
|
||||
echo "gstack-upgrade: reinstalled $(head -1 "$ROOT/VERSION"); only the wrapper row failed (an owner action on the Drive copy, CAPY_WRAPPER_STALE), so no reinstall is pending"
|
||||
fi
|
||||
if [ "$_rc" -eq 0 ]; then
|
||||
[ -n "$CALLER_PROJECT" ] || cat "$_held"
|
||||
rm -f "$PENDING" "$_held" "$_ierr"; certify
|
||||
fi
|
||||
[ -s "$_held" ] && cat "$_held"
|
||||
[ "$_rc" -eq 3 ] && [ ! -s "$_held" ] && stop_trailer "$_ierr"
|
||||
rm -f "$_held" "$_ierr"
|
||||
[ -f "$PENDING" ] || exit 1
|
||||
echo "gstack-upgrade: the checkout is at $(head -1 "$ROOT/VERSION") but its reinstall failed (exit $_rc); fix: re-run $ROOT/bin/gstack-upgrade (it completes the pending reinstall)" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
+1
-1
@@ -16,4 +16,4 @@ GSTACK_MIN_REVISION=1.91.96.0
|
||||
# GSTACK_CAPY_MIN_WRAPPER: the oldest bootstrapper revision (GSTACK_CAPY_WRAPPER in
|
||||
# capy/skill/gstack/scripts/install.sh) the owner should run. An older copy still
|
||||
# works and prints an owner action (CAPY_WRAPPER_STALE).
|
||||
GSTACK_CAPY_MIN_WRAPPER=2
|
||||
GSTACK_CAPY_MIN_WRAPPER=1
|
||||
@@ -829,7 +829,7 @@ describe('gstack-doctor --check: wrapper and runtime drift', () => {
|
||||
expect(parse(f, [], { GSTACK_CAPY_WRAPPER_PATH: stale }).trailer).toBe('gstack: fail wrapper');
|
||||
});
|
||||
|
||||
test('the printed workaround certifies: a current active wrapper passes with the stale Drive copy unchanged (WARN, owner action); a stale active wrapper fails', () => {
|
||||
test('the printed workaround certifies: a current active wrapper passes with the stale Drive copy unchanged (WARN, owner action); a stale active wrapper is a WARN too', () => {
|
||||
const f = makeFixture();
|
||||
withSupport(f);
|
||||
const stale = drive(f, 'user-garry-tan', PRE_BOOTSTRAPPER);
|
||||
@@ -844,8 +844,11 @@ describe('gstack-doctor --check: wrapper and runtime drift', () => {
|
||||
expect(json.rows.find((x: { id: string }) => x.id === 'wrapper')).toMatchObject({ status: 'WARN', code: 'CAPY_WRAPPER_STALE' });
|
||||
withSupport(f, 2);
|
||||
const old = parse(f, [], { GSTACK_CAPY_WRAPPER_REV: '1', GSTACK_CAPY_WRAPPER_PATH: stale });
|
||||
expect(old.trailer).toBe('gstack: fail wrapper');
|
||||
expect(old.line('wrapper')).toContain(`FAIL wrapper — the active Capy skill copy ${stale} is wrapper 1, minimum 2`);
|
||||
// Integration item 2: an active copy below the minimum still works, so it
|
||||
// is an owner-action WARN and the trailer stays ok.
|
||||
expect(old.status, old.out).toBe(0);
|
||||
expect(old.trailer).toMatch(/^gstack: ok /);
|
||||
expect(old.line('wrapper')).toBe(`WARN wrapper — the active Capy skill copy ${stale} is wrapper 1, minimum 2 (it still works); owner action: copy ${f.root}/capy/skill/gstack/ over the Drive skill folder, or have Capy re-sync it; this recurs every session until the Drive copy changes (CAPY_WRAPPER_STALE; https://github.com/garrytan/gstack/blob/main/docs/troubleshooting.md#capy-wrapper-stale)`);
|
||||
});
|
||||
|
||||
test('a drifted Bun resolution is a runtime FAIL carrying the installer\'s detail, fix and BUN_RESOLUTION_DRIFT', () => {
|
||||
|
||||
@@ -238,8 +238,12 @@ describe('gstack-upgrade', () => {
|
||||
'echo "capy-install $* cwd=$(pwd -P)" >> "$UPGRADE_LOG"',
|
||||
'case " $* " in *" --project "*)',
|
||||
' [ -n "${STUB_PIN_CONFLICT:-}" ] && { echo "gstack-capy-install: Bun conflict: gstack needs >=1.4.2 (verified archives: 1.4.2 1.4.3), $2/package.json pins exactly 1.3.14; fix: raise the project\'s pin (RUNTIME_PIN_CONFLICT; https://github.com/garrytan/gstack/blob/main/docs/troubleshooting.md#capy-install-runtime-conflict)" >&2; exit 3; }',
|
||||
' [ -n "${STUB_PINS_FAIL:-}" ] && { echo "FAIL pins — bun 1.4.2 outside CI 1.4.3; fix: x (PROJECT_PIN_MISMATCH; d)"; echo "gstack: fail pins"; exit 1; } ;;',
|
||||
' [ -n "${STUB_PINS_FAIL:-}" ] && { echo "FAIL pins — bun 1.4.2 outside CI 1.4.3; fix: x (PROJECT_PIN_MISMATCH; d)"; echo "gstack: fail pins"; exit 1; }',
|
||||
' [ -n "${STUB_PROJECT_STOP:-}" ] && { echo "gstack-capy-install: preflight: jq not found; fix: install jq (INSTALL_PREFLIGHT_FAILED; d)" >&2; exit 3; } ;;',
|
||||
'esac',
|
||||
'echo "wrapper-env rev=${GSTACK_CAPY_WRAPPER_REV:-} path=${GSTACK_CAPY_WRAPPER_PATH:-}" >> "$UPGRADE_LOG.env"',
|
||||
'[ -n "${STUB_STOP:-}" ] && { echo "gstack-capy-install: preflight: curl not found; fix: install curl (INSTALL_PREFLIGHT_FAILED; d)" >&2; exit 3; }',
|
||||
'[ -n "${STUB_WRAPPER_FAIL:-}" ] && { echo "FAIL wrapper — every Capy skill copy is stale; fix: x (CAPY_WRAPPER_STALE; d)"; echo "gstack: fail wrapper"; exit 1; }',
|
||||
'[ -f "${STUB_FAIL_ONCE:-/nonexistent}" ] && { rm -f "$STUB_FAIL_ONCE"; echo "gstack: fail setup — ./setup failed"; exit 1; }',
|
||||
'echo "gstack: ok $(cat "$(dirname "$0")/../VERSION") for=autoplan"',
|
||||
].join('\n') + '\n';
|
||||
@@ -305,6 +309,35 @@ describe('gstack-upgrade', () => {
|
||||
expect(f.calls().at(-1)).toBe(`capy-install --project ${f.project} cwd=${f.project}`);
|
||||
});
|
||||
|
||||
// Integration items 2 and 9: the wrapper row is never a reinstall failure,
|
||||
// and every installer stop line becomes a trailer.
|
||||
test('a reinstall whose only failing row is wrapper leaves no pending marker; the installer runs with the checkout copy as the active wrapper', () => {
|
||||
const f = capyFixture();
|
||||
write(path.join(f.root, 'capy/skill/gstack/scripts/install.sh'), 'GSTACK_CAPY_WRAPPER=7\n');
|
||||
const r = f.from(fs.realpathSync(f.host.base), [], { STUB_WRAPPER_FAIL: '1' });
|
||||
expect(r.status).toBe(1);
|
||||
expect(r.stdout).toContain('gstack: fail wrapper');
|
||||
expect(r.stdout).toContain('only the wrapper row failed (an owner action on the Drive copy, CAPY_WRAPPER_STALE), so no reinstall is pending');
|
||||
expect(r.stderr).not.toContain('completes the pending reinstall');
|
||||
expect(fs.existsSync(f.pending)).toBe(false);
|
||||
const again = f.from(fs.realpathSync(f.host.base));
|
||||
expect(again.stdout).not.toContain('reinstall pending');
|
||||
expect(fs.readFileSync(path.join(f.host.base, 'calls.log.env'), 'utf8')).toContain(`rev=7 path=${f.root}/capy/skill/gstack/scripts/install.sh`);
|
||||
});
|
||||
|
||||
test('an installer stop line maps to `gstack: fail runtime — <msg>`: in the caller-project check (no pending) and in the reinstall (pending kept)', () => {
|
||||
const f = capyFixture();
|
||||
const cert = f.from(f.project, [], { STUB_PROJECT_STOP: '1' });
|
||||
expect(cert.status).toBe(1);
|
||||
expect(cert.stdout.trim().split('\n').at(-1)).toBe('gstack: fail runtime — preflight: jq not found; fix: install jq (INSTALL_PREFLIGHT_FAILED; d)');
|
||||
expect(fs.existsSync(f.pending)).toBe(false);
|
||||
const g = capyFixture();
|
||||
const re = g.from(g.project, [], { STUB_STOP: '1' });
|
||||
expect(re.status).toBe(1);
|
||||
expect(re.stdout.trim().split('\n').at(-1)).toBe('gstack: fail runtime — preflight: curl not found; fix: install curl (INSTALL_PREFLIGHT_FAILED; d)');
|
||||
expect(fs.existsSync(g.pending)).toBe(true);
|
||||
});
|
||||
|
||||
test('off Capy the caller\'s project is certified by the doctor\'s runtime and pins rows', () => {
|
||||
const f = fixture();
|
||||
write(path.join(f.root, 'bin/gstack-doctor'), '#!/usr/bin/env bash\necho "doctor $*" >> "$UPGRADE_LOG"\n[ -n "${STUB_PINS_FAIL:-}" ] && { echo "FAIL pins — x"; echo "gstack: fail pins"; exit 1; }\necho "gstack: ok 1.0.1 for=require=runtime,pins"\n', 0o755);
|
||||
|
||||
Reference in new issue
Block a user