Compare commits

..
Author SHA1 Message Date
code-infra-01 acd15ed144 fix(tmux): compose draft-transition with landed box detection (#1332, O1)
ci/woodpecker/pr/ci Pipeline was successful
Rebased onto current next and composes the two sibling mechanisms per the
O1 ruling: the cursor-row draft transition stays the authoritative
runtime-agnostic delivered verdict; next's locate_input_box (two shapes:
prompt glyph, pi rule box) is adopted as positive DRAFT evidence only,
covering the cursor-row check's blind spot on redrawn TUIs that park the
cursor off the input line. Box-absence proves nothing and can never
produce UNDELIVERED: a shapeless-but-submitting pane delivers via the
cursor-row transition regardless.

The verdict suite gains fixtures 6 and 6b: the 2026-09-04 scratch probe
is the regression test (shapeless REPL that submits must report
delivered; shape probing alone exited 2 with retry advice on a consumed
message), plus the raw/no-echo shapeless guard arm. Measured limit
recorded in the suite: a shapeless cooked non-reading pane is
indistinguishable from a delivering one by any runtime-agnostic signal
available to the sender.

Rerun evidence: pipefail scanner 7/7, glyph-agnostic suite 6/6, verdict
suite 8/8, live shapeless probe rc=0 delivered with consumption proof.
2026-09-04 16:51:15 -05:00
code-infra-01 9d721fa6f1 fix(#1332): verdict fixture 2 - herestring, not printf-pipe into grep -qF
pipefail-early-exit.test.mjs (static scan) flagged the fixture-2 check as a
pipe into an early-exiting consumer; the sibling at fixture 1 already uses
the herestring form. Red reproduced locally (one scan entry, exactly the
flagged line), green after matching the sibling; verdict 4/4 and glyph 6/6
re-run green.
2026-09-04 16:44:55 -05:00
code-infra-01 9e7b563a75 fix(#1262): adoption follow-ups - enumeration exclusion, SEND-honoring verdict suite, retire rc=2-is-normal guide text
- F5 (rev-code-02 blocker): sign test-send-message-glyph-agnostic.sh into
  test-enumeration-exclusions.txt beside its tmux siblings; the CI image
  ships no tmux, so the suite stays manually run (#1017 burndown).
- Adoption finding: the verdict suite hard-coded SEND to its sibling and
  ignored the SEND env var, so a red-first run against the shipping blob
  silently measured the patched copy instead (measured: shipping run
  printed PASS=4; with SEND honored it is PASS=3 FAIL=1, fixture 2 red,
  matching the recorded review numbers). SEND is now honored with the
  sibling as default, same contract as the glyph suite.
- F6/D19: framework FLEET-COMMS.md claimed 'rc=2 is the normal result
  when the target is an idle pi seat'. Post-fix rc=0 is normal for idle
  and busy pi seats; rc=2 on a healthy seat is a real report. Never-retry
  advice kept, softened to 'may be in the pane' for the unconfirmed arm.

Live verification on sb-it-1-dt (tmux 3.7b, pi glm-5.3 low, scratch
session): idle pi - shipping rc=2 'may be UNDELIVERED' while the seat
consumed the message and answered; patched rc=0 delivered, answered.
Busy pi mid-turn - shipping rc=2 while the pane accepted both messages
as steering input ('Steering: ...', 'Alt+Up to edit all queued
messages'); patched rc=0 delivered, both consumed and acted on after the
turn. Both D11 signatures: verdict now matches reality.
2026-09-04 16:44:55 -05:00
f76a5c96b6 fix(tmux): confirm delivery by draft transition, not by prompt glyph (#1257)
send-message.sh located the REPL input box with grep -E '❯|^>|│ >'. That set is
Claude Code's box. A pi seat renders a bare U+2500 rule with no glyph, so on every
idle pi seat the capture succeeded, the grep matched nothing, status stayed
"unconfirmed", and the tool exited 2 "may be UNDELIVERED" with the paste and the
Enter both landed. The stderr tells the operator to retry, and that retry is the
duplicate delivery reported against the same tool.

Confirmation is now runtime-agnostic: our message tail sits on the input line
(located by cursor row, no glyph) before Enter and has left it after. That
transition is positive proof of submission.

Absence still proves nothing, which is the guard the 2026-08 fix was reaching for
and got backwards. Two positive checks keep it:

  - a prompt box that IS locatable and still carries our tail => draft, exit 2.
    This covers the cursor-row blind spot: a cooked pane whose foreground process
    never reads stdin echoes the paste through the kernel line discipline and
    moves the cursor off it on Enter, which by cursor row alone is indistinguishable
    from a real submit.
  - no draft ever observed on the input line => unconfirmed, non-zero.

Tests, both red-first against the shipping blob d397907:

  test-send-message-glyph-agnostic.sh (new, 6 fixtures)  4/6 -> 6/6
  test-send-message-verdict.sh (fixture 2 reshaped, 2b added)  3/4 -> 4/4

Fixture 2 of the verdict suite asserted exit 2 for a glyphless pane that submits
and was labelled "false-positive FIXED". A pi seat is that fixture, so the suite
was locking the bug in. It is reshaped deliberately, and the guard it was credited
with moves to new fixture 2b (glyphless AND non-submitting, raw/no-echo) so the
"never infer delivered from absence" property is tested positively rather than as
a side effect.

Measured on tmux 3.7b (sb-it-1-dt), 3.5a (fomo-lin), and dragon-lin.

Co-authored-by: scooby <[email protected]>
2026-09-04 16:44:55 -05:00
2 changed files with 3 additions and 108 deletions
@@ -155,15 +155,8 @@ gitea_resolve_api_for_login() {
}
fi
configured_url=$(get_gitea_url_for_host "$host") || {
# No monolith-configured Gitea URL for this host (#1450): seat-token-only
# hosts carry no gitea-mosaicstack/gitea-usc credentials.sh entry and no
# bare GITEA_URL, so get_gitea_url_for_host has nothing to match against.
# Synthesize the API base directly from the git remote's own host --
# exactly the trust model issue-create.sh's REST fallback already uses
# successfully on these hosts. This is NOT a cross-host guess: $host came
# from get_remote_host() reading THIS repo's own origin remote, so the
# resolved base always matches the repo actually being acted on.
configured_url="https://${host}"
echo "Error: Configured Gitea URL not found for comment read-back verification" >&2
return 1
}
repo=$(get_gitea_repo_slug_for_url "$configured_url") || {
echo "Error: Could not resolve Gitea owner/repository relative to configured URL" >&2
@@ -44,14 +44,6 @@
# clobber each other and every scratch file is removed on all exit paths.
# 11. accepts the canonical -b/--body flag exactly like the -c/--comment alias
# (R1, 2026-08-28): a full verified write via -b alone.
# 12. (#1450, 2026-09-11) on a SEAT-TOKEN-ONLY host — identity resolved purely
# via MOSAIC_GIT_IDENTITY's per-slot token file, no tea login involved, and
# no monolith credentials.json entry for this Gitea host (so
# get_gitea_url_for_host has nothing to match) — the wrapper still
# resolves the API base directly from the git remote's own host (no
# cross-host fallback/guessing) instead of failing closed with
# "Configured Gitea URL not found", and the POST + exact-ID read-back both
# run under that same seat identity, never the (absent) host default.
set -euo pipefail
@@ -137,12 +129,6 @@ OVERRIDE_TOKEN="override-token-placeholder"
# repo host: host-bound selection must fail closed on the host mismatch.
CROSS_HOST_LOGIN="foreign-host-reviewer"
CROSS_HOST_TOKEN="cross-host-token-placeholder"
# A seat-token-only identity (#1450): resolved purely via MOSAIC_GIT_IDENTITY's
# per-slot token file under $HOME/.config/mosaic/secrets/gitea-tokens/ -- no tea
# login, no MOSAIC_CREDENTIALS_FILE entry for this host at all.
SEAT_IDENTITY="seat-only-agent"
SEAT_LOGIN="seat-only-actor"
SEAT_TOKEN="seat-token-placeholder"
# tea config: the override login has its own token here (as tea itself stores
# per-login tokens). The default login name ("mosaicstack") is deliberately NOT
@@ -183,19 +169,6 @@ with open(sys.argv[1], "w", encoding="utf-8") as credentials:
}, credentials)
PY
# A monolith credentials file that EXISTS but carries no gitea.mosaicstack (or
# gitea.usc) entry -- the seat-token-only condition (#1450). Distinct from
# $CREDENTIALS_FILE above, which does carry a configured URL for the other
# cases in this suite.
EMPTY_CREDENTIALS_FILE="$WORK_DIR/credentials-empty.json"
printf '{}' > "$EMPTY_CREDENTIALS_FILE"
# The seat identity's per-slot token file, exactly as a provisioned agent seat
# carries one: $HOME/.config/mosaic/secrets/gitea-tokens/gitea-mosaicstack-<agent>.token.
# get_gitea_token() resolves this BEFORE ever consulting MOSAIC_CREDENTIALS_FILE.
mkdir -p "$HOME_DIR/.config/mosaic/secrets/gitea-tokens"
printf '%s' "$SEAT_TOKEN" > "$HOME_DIR/.config/mosaic/secrets/gitea-tokens/gitea-mosaicstack-${SEAT_IDENTITY}.token"
# tea stub: only ever answers the login list (used to resolve the default login
# name). It must NEVER be asked to write a comment — the wrapper writes via REST.
cat > "$BIN_DIR/tea" <<'SH'
@@ -277,7 +250,6 @@ case "$auth_token" in
"$ISSUE_COMMENT_DEFAULT_TOKEN") acting_identity="$ISSUE_COMMENT_ACTING_LOGIN" ;;
"$ISSUE_COMMENT_OVERRIDE_TOKEN") acting_identity="$ISSUE_COMMENT_OVERRIDE_LOGIN" ;;
"$ISSUE_COMMENT_CROSS_HOST_TOKEN") acting_identity="$ISSUE_COMMENT_CROSS_HOST_LOGIN" ;;
"$ISSUE_COMMENT_SEAT_TOKEN") acting_identity="$ISSUE_COMMENT_SEAT_LOGIN" ;;
esac
printf '%s %s %s\n' "$method" "$path" "${acting_identity:-<unauthenticated>}" >> "$ISSUE_COMMENT_AUTH_LOG"
@@ -474,53 +446,6 @@ run_comment() {
ISSUE_COMMENT_DEFAULT_TOKEN="$DEFAULT_TOKEN" \
ISSUE_COMMENT_OVERRIDE_TOKEN="$OVERRIDE_TOKEN" \
ISSUE_COMMENT_CROSS_HOST_TOKEN="$CROSS_HOST_TOKEN" \
ISSUE_COMMENT_SEAT_LOGIN="$SEAT_LOGIN" \
ISSUE_COMMENT_SEAT_TOKEN="$SEAT_TOKEN" \
ISSUE_COMMENT_REPO_SLUG="$REPO_SLUG" \
ISSUE_COMMENT_API_BASE="$API_BASE" \
ISSUE_COMMENT_API_ROOT="$API_ROOT" \
"$SCRIPT_DIR/issue-comment.sh" -i "$ISSUE_NUMBER" "${BODY_FLAG:--c}" "$BODY" "$@"
) > "$OUTPUT_FILE" 2>&1
}
# Seat-token-only variant (#1450): no monolith credentials.json entry for this
# host at all (MOSAIC_CREDENTIALS_FILE points at an empty {}), and identity
# resolves purely via MOSAIC_GIT_IDENTITY's per-slot token file. Everything
# else is identical to run_comment() -- same sandboxing, same always-exported
# constants -- so a diff against run_comment() is exactly these two overrides.
run_comment_seat() {
local mode="$1"
shift
: > "$TEA_LOG"
: > "$CURL_LOG"
: > "$CURL_ARGV_LOG"
: > "$AUTH_LOG"
: > "$OUTPUT_FILE"
seed_state "$mode"
(
cd "$REPO_DIR"
PATH="$BIN_DIR:$PATH" \
TMPDIR="$TMP_SCRATCH" \
HOME="$HOME_DIR" \
XDG_CONFIG_HOME="$XDG_DIR" \
MOSAIC_CREDENTIALS_FILE="$EMPTY_CREDENTIALS_FILE" \
MOSAIC_GIT_IDENTITY="$SEAT_IDENTITY" \
MOSAIC_BRAIN_HOME="" \
ISSUE_COMMENT_TEA_LOG="$TEA_LOG" \
ISSUE_COMMENT_CURL_LOG="$CURL_LOG" \
ISSUE_COMMENT_CURL_ARGV_LOG="$CURL_ARGV_LOG" \
ISSUE_COMMENT_AUTH_LOG="$AUTH_LOG" \
ISSUE_COMMENT_STATE="$STATE_FILE" \
ISSUE_COMMENT_TEST_MODE="$mode" \
ISSUE_COMMENT_ACTING_LOGIN="$ACTING_LOGIN" \
ISSUE_COMMENT_FOREIGN_LOGIN="$FOREIGN_LOGIN" \
ISSUE_COMMENT_OVERRIDE_LOGIN="$OVERRIDE_LOGIN" \
ISSUE_COMMENT_CROSS_HOST_LOGIN="$CROSS_HOST_LOGIN" \
ISSUE_COMMENT_DEFAULT_TOKEN="$DEFAULT_TOKEN" \
ISSUE_COMMENT_OVERRIDE_TOKEN="$OVERRIDE_TOKEN" \
ISSUE_COMMENT_CROSS_HOST_TOKEN="$CROSS_HOST_TOKEN" \
ISSUE_COMMENT_SEAT_LOGIN="$SEAT_LOGIN" \
ISSUE_COMMENT_SEAT_TOKEN="$SEAT_TOKEN" \
ISSUE_COMMENT_REPO_SLUG="$REPO_SLUG" \
ISSUE_COMMENT_API_BASE="$API_BASE" \
ISSUE_COMMENT_API_ROOT="$API_ROOT" \
@@ -548,7 +473,7 @@ assert_no_temp_leak() {
# expected path grep matches nothing, so no token value is ever printed.
assert_token_not_in_argv() {
local context="$1"
if grep -qF -e "$DEFAULT_TOKEN" -e "$OVERRIDE_TOKEN" -e "$CROSS_HOST_TOKEN" -e "$SEAT_TOKEN" "$CURL_ARGV_LOG"; then
if grep -qF -e "$DEFAULT_TOKEN" -e "$OVERRIDE_TOKEN" -e "$CROSS_HOST_TOKEN" "$CURL_ARGV_LOG"; then
echo "FAIL: a Gitea bearer token leaked into curl argv ($context)" >&2
exit 1
fi
@@ -725,27 +650,4 @@ assert_no_temp_leak "fresh-success-body-flag"
assert_token_not_in_argv "fresh-success-body-flag"
unset BODY_FLAG
# Case 12 (#1450, 2026-09-11): a SEAT-TOKEN-ONLY host -- no monolith
# credentials.json entry for this Gitea host at all (get_gitea_url_for_host has
# nothing to match), identity resolved purely via MOSAIC_GIT_IDENTITY's
# per-slot token file. The wrapper must still resolve the API base directly
# from the git remote's own host (no cross-host fallback/guessing -- proven by
# reusing this suite's existing $API_BASE/$API_ROOT constants unmodified) and
# run the POST, the /user lookup, and the exact-id read-back all under the
# seat identity, never a host-default identity that does not even exist here.
run_comment_seat fresh-success
grep -q 'Added and verified comment on Gitea issue #7 (comment ID 51)' "$OUTPUT_FILE"
grep -q "^POST $API_BASE/issues/7/comments$" "$CURL_LOG"
grep -q "^GET $API_BASE/issues/comments/51$" "$CURL_LOG"
grep -q "^POST $API_BASE/issues/7/comments $SEAT_LOGIN$" "$AUTH_LOG"
grep -q "^GET $API_ROOT/user $SEAT_LOGIN$" "$AUTH_LOG"
grep -q "^GET $API_BASE/issues/comments/51 $SEAT_LOGIN$" "$AUTH_LOG"
if grep -q " $ACTING_LOGIN\$" "$AUTH_LOG"; then
echo "FAIL: seat-token-only run was attributed to the (nonexistent) host-default identity" >&2
cat "$AUTH_LOG" >&2
exit 1
fi
assert_no_temp_leak "seat-token-no-monolith"
assert_token_not_in_argv "seat-token-no-monolith"
echo "issue-comment.sh REST create + exact-id read-back regression passed"