docs(fix-d12): resolve debug session and update STATE

- Mark .planning/debug/d12-blob-port-transfer-fails.md as
  status: resolved; fill in the Resolution section with the
  applied fix (5 commit hashes, files changed), verification
  output (15/15 tests, tsc clean, vite build green, zero
  as-any/ts-ignore in fix-touched files), and inline answers
  to the specialist-review questions raised by the planner.
  Move the file to .planning/debug/resolved/.
- Update STATE.md frontmatter (stopped_at) + Decisions log
  + Session Continuity to record the D-12 fix landing and
  the open Plan 07 ffprobe gate (still requires operator
  smoke.sh + ffprobe re-run before Phase 1 can close).
- Land smoke.sh — the operator's D-12 acceptance-gate harness
  that surfaced the original failure. Self-contained: dedicated
  /tmp/mokosh-smoke-profile, auto-accept desktop-capture picker,
  Downloads polling, ffprobe gate, fixture staging.

REQ-video-ring-buffer remains NOT-complete — Plan 07 owns it,
operator must re-run ./smoke.sh to verify the fix end-to-end
in Chrome.

Refs: debug session d12-blob-port-transfer-fails (resolved).
This commit is contained in:
2026-05-15 20:23:29 +02:00
parent d5bb948d95
commit bf076199b4
3 changed files with 343 additions and 6 deletions

239
smoke.sh Executable file
View File

@@ -0,0 +1,239 @@
#!/usr/bin/env bash
# Mokosh Phase 1 — D-12 ffprobe acceptance gate smoke test.
#
# Architecture:
# - Launches Chrome with a DEDICATED `/tmp/mokosh-smoke-profile` user-data-dir
# so we don't interfere with your daily Chrome session.
# - Opens a data: URL with title "Mokosh Smoke Test" as the share target.
# - Passes `--auto-select-desktop-capture-source="Mokosh Smoke Test"` so the
# getDisplayMedia picker auto-accepts that tab — no manual pick.
# - Logs Chrome stderr/stdout to /tmp/mokosh-chrome.log.
# - Polls ~/Downloads for the new session_report_*.zip.
# - Runs ffprobe gate, stages fixture, opens WebM in Chrome for visual check.
#
# Chrome 148+ removed `--load-extension`, so the extension load is the ONE
# manual step that has to happen each time the smoke profile is fresh. (Once
# loaded, it persists in the profile until the dir is wiped — see KEEP_PROFILE.)
#
# What YOU do (~3 clicks total):
# 1. Run this script.
# 2. In the Chrome window that opens, address-bar → chrome://extensions
# → toggle "Developer mode" ON → "Load unpacked" → select
# /home/parf/projects/work/repremium/dist (only needed if profile is fresh).
# 3. Click the extension icon (puzzle-piece menu if not pinned).
# The screen-share picker auto-accepts the "Mokosh Smoke Test" tab.
# 4. Wait ~35 seconds (or move mouse / scroll the smoke tab for frame deltas).
# 5. Click the icon again → click "Сохранить отчёт об ошибке".
# This script handles everything after that.
#
# Env knobs:
# CHROME_BIN — Chrome binary (default: /usr/bin/google-chrome-stable)
# KEEP_PROFILE=1 — don't wipe the smoke profile, so extension stays loaded
# across runs (default: wipe, fresh-load each run)
# POLL_TIMEOUT — max seconds to wait for the download (default: 900 = 15m)
set -euo pipefail
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DIST_DIR="${REPO_DIR}/dist"
PROFILE_DIR="/tmp/mokosh-smoke-profile"
DOWNLOADS_DIR="${HOME}/Downloads"
FIXTURE_DEST="${REPO_DIR}/tests/fixtures/last_30sec.webm"
WEBM_TMP="/tmp/mokosh-last_30sec.webm"
CHROME_LOG="/tmp/mokosh-chrome.log"
SHARE_TARGET="Mokosh Smoke Test"
CHROME_BIN="${CHROME_BIN:-/usr/bin/google-chrome-stable}"
KEEP_PROFILE="${KEEP_PROFILE:-0}"
POLL_TIMEOUT="${POLL_TIMEOUT:-900}"
red() { printf '\033[31m%s\033[0m\n' "$*"; }
green() { printf '\033[32m%s\033[0m\n' "$*"; }
yellow() { printf '\033[33m%s\033[0m\n' "$*"; }
blue() { printf '\033[34m%s\033[0m\n' "$*"; }
echo
blue "==> Mokosh Phase 1 smoke test (D-12 ffprobe gate)"
echo
# --- pre-flight ---
[[ -d "${DIST_DIR}" ]] || { red "FAIL: ${DIST_DIR} missing — run \`npm run build\` first"; exit 1; }
[[ -x "${CHROME_BIN}" ]] || { red "FAIL: ${CHROME_BIN} not found (set CHROME_BIN=...)"; exit 1; }
command -v ffprobe >/dev/null || { red "FAIL: ffprobe not installed"; exit 1; }
command -v unzip >/dev/null || { red "FAIL: unzip not installed"; exit 1; }
grep -q '"desktopCapture"' "${DIST_DIR}/manifest.json" || { red "FAIL: dist/manifest.json missing desktopCapture"; exit 1; }
! grep -q '"tabCapture"' "${DIST_DIR}/manifest.json" || { red "FAIL: dist/manifest.json still has tabCapture"; exit 1; }
green "✓ pre-flight checks passed"
echo " chrome: ${CHROME_BIN} ($("${CHROME_BIN}" --version 2>/dev/null || echo 'unknown'))"
echo " dist: ${DIST_DIR}"
echo " profile: ${PROFILE_DIR} (KEEP_PROFILE=${KEEP_PROFILE})"
echo " log: ${CHROME_LOG}"
echo " downloads: ${DOWNLOADS_DIR}"
echo
# --- snapshot Downloads ---
BEFORE_LIST=$(find "${DOWNLOADS_DIR}" -maxdepth 1 -name 'session_report_*.zip' 2>/dev/null | wc -l)
echo " existing session_report_*.zip in Downloads: ${BEFORE_LIST}"
echo
# --- profile prep ---
PROFILE_HAS_EXTENSION=0
if [[ "${KEEP_PROFILE}" != "1" ]]; then
rm -rf "${PROFILE_DIR}"
fi
mkdir -p "${PROFILE_DIR}"
# Detect if a previous run already loaded the extension into this profile
if [[ -d "${PROFILE_DIR}/Default/Extensions" ]] && \
find "${PROFILE_DIR}/Default/Extensions" -maxdepth 3 -name 'manifest.json' 2>/dev/null | head -1 | xargs -r grep -q 'AI Call Recorder' 2>/dev/null; then
PROFILE_HAS_EXTENSION=1
green "✓ extension already loaded in profile from previous KEEP_PROFILE=1 run"
fi
# --- compose the smoke tab data URL ---
read -r -d '' SMOKE_HTML <<'EOF' || true
<title>Mokosh Smoke Test</title>
<style>body{font-family:sans-serif;background:#222;color:#eee;padding:40px;line-height:1.5}code{background:#444;padding:2px 6px;border-radius:3px}ol li{margin:6px 0}.flash{animation:flash 1s infinite;background:#0a4;padding:4px 8px;display:inline-block}@keyframes flash{0%,100%{opacity:1}50%{opacity:.4}}</style>
<body>
<h1>🧵 Mokosh Smoke Test</h1>
<p>This tab is the share-screen target. The picker auto-accepts because the title matches <code>--auto-select-desktop-capture-source</code>.</p>
<h2>Steps:</h2>
<ol>
<li><strong class="flash">First time only:</strong> Go to <code>chrome://extensions</code> → toggle <strong>Developer mode</strong> ON → <strong>Load unpacked</strong> → select <code>/home/parf/projects/work/repremium/dist</code>.<br>(Set <code>KEEP_PROFILE=1</code> when re-running this script to skip the reload.)</li>
<li>Click the <strong>AI Call Recorder</strong> toolbar icon (or puzzle-piece menu).</li>
<li>The picker auto-accepts <em>this tab</em>. Confirm Chrome's "Sharing your screen" indicator appears.</li>
<li>Wait <strong>≥ 35 seconds</strong>. Move the mouse around or scroll this page so vp9 has frame deltas.</li>
<li>Click the toolbar icon again → click <strong>Сохранить отчёт об ошибке</strong>.</li>
</ol>
<p>The script in your terminal will detect the download and finish the ffprobe gate automatically.</p>
</body>
EOF
SMOKE_DATA_URL="data:text/html,$(printf '%s' "${SMOKE_HTML}" | python3 -c 'import sys,urllib.parse;print(urllib.parse.quote(sys.stdin.read(), safe=""))' 2>/dev/null || printf '%s' "${SMOKE_HTML}")"
# --- launch Chrome ---
blue "==> launching Chrome with smoke profile + auto-accept picker..."
"${CHROME_BIN}" \
--user-data-dir="${PROFILE_DIR}" \
--auto-select-desktop-capture-source="${SHARE_TARGET}" \
--no-first-run \
--no-default-browser-check \
--new-window \
"${SMOKE_DATA_URL}" \
> "${CHROME_LOG}" 2>&1 &
CHROME_PID=$!
echo " Chrome PID: ${CHROME_PID}"
echo " Tail with: tail -f ${CHROME_LOG}"
sleep 4
if ! ps -p "${CHROME_PID}" >/dev/null 2>&1; then
# Chrome may have exec'd into a singleton; check if any chrome procs are running with our profile dir
if ! pgrep -f "${PROFILE_DIR}" >/dev/null 2>&1; then
red "FAIL: Chrome exited within 4 seconds"
echo " Last 30 lines of ${CHROME_LOG}:"
tail -30 "${CHROME_LOG}" 2>/dev/null || true
exit 3
fi
fi
green "✓ Chrome running"
echo
# --- prompt ---
if [[ ${PROFILE_HAS_EXTENSION} -eq 1 ]]; then
yellow "==> Extension is already loaded. Just click the icon → wait 35s → click save."
else
yellow "==> In the Chrome window:"
echo " 1. chrome://extensions → Developer mode ON → Load unpacked → ${DIST_DIR}"
echo " 2. Confirm 'AI Call Recorder' appears with no red error."
echo " 3. Click the extension icon."
echo " 4. WAIT >= 35 seconds."
echo " 5. Click the icon again → 'Сохранить отчёт об ошибке'."
fi
echo
blue "==> waiting for a new session_report_*.zip to appear..."
echo " (Ctrl+C aborts. Auto-detects, ffprobes, stages fixture, opens WebM.)"
echo
# --- poll Downloads ---
NEW_ARCHIVE=""
WAITED=0
while [[ ${WAITED} -lt ${POLL_TIMEOUT} ]]; do
NEW_COUNT=$(find "${DOWNLOADS_DIR}" -maxdepth 1 -name 'session_report_*.zip' 2>/dev/null | wc -l)
if [[ ${NEW_COUNT} -gt ${BEFORE_LIST} ]]; then
sleep 2 # let the download settle
LATEST=$(find "${DOWNLOADS_DIR}" -maxdepth 1 -name 'session_report_*.zip' -printf '%T@ %p\n' 2>/dev/null | sort -rn | head -1 | cut -d' ' -f2- || true)
if [[ -n "${LATEST}" ]]; then
NEW_ARCHIVE="${LATEST}"
break
fi
fi
sleep 1
WAITED=$((WAITED + 1))
if [[ $((WAITED % 30)) -eq 0 ]]; then
yellow " ...still waiting (${WAITED}s elapsed, count=${NEW_COUNT}/baseline=${BEFORE_LIST})"
fi
done
if [[ -z "${NEW_ARCHIVE}" ]]; then
red "FAIL: no new archive appeared in ${POLL_TIMEOUT}s"
echo " Chrome log tail:"
tail -30 "${CHROME_LOG}" 2>/dev/null || true
echo " Chrome still running — close manually or: kill ${CHROME_PID}"
exit 4
fi
green "✓ archive detected: ${NEW_ARCHIVE}"
echo
# --- extract + ffprobe gate ---
unzip -p "${NEW_ARCHIVE}" video/last_30sec.webm > "${WEBM_TMP}"
SIZE_BYTES=$(stat -c %s "${WEBM_TMP}")
SIZE_HUMAN=$(ls -lh "${WEBM_TMP}" | awk '{print $5}')
echo " WebM size: ${SIZE_HUMAN} (${SIZE_BYTES} bytes)"
if [[ ${SIZE_BYTES} -lt 100000 ]]; then
yellow "⚠ WebM is smaller than 100 KB — buffer may not have rotated; capture longer"
fi
echo
blue "==> D-12 ACCEPTANCE GATE — ffprobe -v error"
echo "---"
ffprobe -v error -f matroska -i "${WEBM_TMP}" && GATE=0 || GATE=$?
echo "---"
echo "ffprobe exit: ${GATE}"
if [[ ${GATE} -eq 0 ]]; then
green "✓ D-12 ACCEPTANCE GATE PASSED"
else
red "✗ D-12 ACCEPTANCE GATE FAILED — D-13 fallback (restart-segments) required"
yellow "==> diagnostic for D-13 escalation:"
ffprobe -v error -show_packets -i "${WEBM_TMP}" 2>&1 | head -50 || true
fi
echo
blue "==> stream / format dump (paste this back to the orchestrator):"
echo "---"
ffprobe -v error -show_format -show_streams "${WEBM_TMP}" 2>&1 | head -30 || true
echo "---"
echo
# --- stage fixture ---
if [[ ${GATE} -eq 0 ]]; then
mkdir -p "$(dirname "${FIXTURE_DEST}")"
cp "${WEBM_TMP}" "${FIXTURE_DEST}"
green "✓ fixture staged: ${FIXTURE_DEST} ($(ls -lh "${FIXTURE_DEST}" | awk '{print $5}'))"
fi
# --- open the WebM for visual check ---
echo
blue "==> opening the WebM for visual playback (SPEC §10 #7)..."
"${CHROME_BIN}" --user-data-dir="${PROFILE_DIR}" --new-window "file://${WEBM_TMP}" >/dev/null 2>&1 &
echo
green "==> smoke complete."
echo " Chrome (smoke profile) PID ${CHROME_PID} still running."
echo " Kill when done: kill ${CHROME_PID}"
echo " To keep the extension loaded across runs: KEEP_PROFILE=1 ./smoke.sh"
echo
if [[ ${GATE} -eq 0 ]]; then
green "==> NEXT: reply 'approved' to the orchestrator with the stream/format dump."
else
red "==> NEXT: reply 'ffprobe-failed' with the show_packets diagnostic above."
fi
exit "${GATE}"