Files
Jarvis/scripts/check-install-order.sh
james.maes 75a4ed6991 feat(09-04): replace NullVoiceAdapters with real voice adapter triad + lock install order (D-09, D-10, BLOCKER-1, BLOCKER-2, WARNING-5)
- New App/Voice/{VoiceOrchestratorAdapter,VoiceTTSAdapter,VoiceBusEmitterAdapter,RejectReasonCopy}.swift
  replaces NullOrchestratorAdapter / NullTTSAdapter / NullBusEmitterAdapter (D-09).
- VoiceOrchestratorAdapter forwards submit/cancelAndSubmit to AgentOrchestrator,
  appends user-side text to TurnTranscriptStore (BLOCKER-1), enqueues a HUDBanner
  on every SubmitOutcome.rejected case via shared RejectReasonCopy (D-10).
- VoiceTTSAdapter wraps an optional TTSEngineActor (engine construction lands
  in a follow-on plan; nil-engine path no-ops gracefully).
- VoiceBusEmitterAdapter wraps an OutboundBatcher whose sink is the WebviewBridge
  so audio-level RMS reaches the HUD's RingMesh pulse.
- AppDelegate.installVoice now uses the production adapters; install order
  in applicationWillFinishLaunching reordered to vision -> agent -> voice
  with voiceInstallTask awaiting agentInstallTask?.value (WARNING-5).
- New broadcaster .voice subscriber in installAgent: per-turn assistantText
  accumulator + turnSourceWasVoice filter so text-originated turns NEVER drive
  emitTurnEnded on the voice adapter (BLOCKER-2).
- SubmitOutcome.superseded extended with newTurnId so cancelAndSubmit callers
  can append user text under the new turn id (BLOCKER-1 user-side closure on
  the cancel path).
- VoiceSubscriberTextTurnFilterTests proves text turns never trigger
  emitTurnEnded; VoiceOrchestratorAdapterTests covers RejectReasonCopy +
  banner id mapping + VoiceBusEmitterAdapter forwarding.
- New scripts/check-install-order.sh + scripts/check-no-null-voice-adapters.sh
  catch regressions structurally.
2026-05-01 20:39:19 -05:00

52 lines
2.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# Phase 9 / Plan 4 / WARNING-5: install order LOCKED in AppDelegate.swift.
#
# vision → agent → voice. Asserts the literal Task spawn lines for the three
# install subsystems appear in that strict order in applicationWillFinishLaunching.
#
# - vision must finish before agent because installAgent's orchestrator
# constructor takes self.visionRouter (Plan 2) + the broadcaster's
# frame-attach release subscriber needs frameAttachController.
# - agent must finish before voice because installVoice's adapters require
# self.agentOrchestrator + self.turnTranscriptStore (both constructed in
# installAgent — Plan 1 / Plan 4).
#
# In addition, voiceInstallTask MUST await agentInstallTask?.value before
# calling installVoice — otherwise the install order can race even though
# the spawn-line ordering is correct.
set -euo pipefail
ROOT="${1:-$(pwd)}"
file="$ROOT/App/AppDelegate.swift"
if [ ! -f "$file" ]; then
echo "FAIL: $file not found"
exit 1
fi
vision_line=$(grep -n 'visionInstallTask = Task' "$file" | head -1 | cut -d: -f1 || true)
agent_line=$(grep -n 'agentInstallTask = Task' "$file" | head -1 | cut -d: -f1 || true)
voice_line=$(grep -n 'voiceInstallTask = Task' "$file" | head -1 | cut -d: -f1 || true)
if [ -z "$vision_line" ] || [ -z "$agent_line" ] || [ -z "$voice_line" ]; then
echo "FAIL: install order tokens missing in $file"
echo " vision: ${vision_line:-MISSING}"
echo " agent: ${agent_line:-MISSING}"
echo " voice: ${voice_line:-MISSING}"
exit 1
fi
if [ "$vision_line" -lt "$agent_line" ] && [ "$agent_line" -lt "$voice_line" ]; then
# Spawn-order is correct. Now verify voiceInstallTask awaits agentInstallTask
# (a strictly stronger guarantee than just the spawn-line ordering).
if ! grep -qE 'await\s+self\?\.\s*agentInstallTask\?\.\s*value' "$file"; then
echo "FAIL: voiceInstallTask does not await agentInstallTask?.value — install can race"
exit 1
fi
echo "PASS: install order is vision($vision_line) → agent($agent_line) → voice($voice_line); voice awaits agent"
exit 0
fi
echo "FAIL: install order violated. vision=$vision_line agent=$agent_line voice=$voice_line"
exit 1