feat: add granite hot-seat review pipeline - #1
Merged
Conversation
added 7 commits
May 7, 2026 01:18
…nits, thermal state for capture-rate root cause
… the share sheet actually surfaces it
… + AAC write — root cause of slow playback + 14% capture rate
…estamps Bug report 2026-05-07 — at minute 01:40, four distinct utterances were visually overwriting each other in a single MEDIC 01:40 bubble. Two collaborating bugs: (1) emitPartial only updated currentPartial; if Parakeet started a new utterance partial without firing setEouCallback (continuous fast speech without a 2 s silence gap), the prior utterance was never committed — the partial bubble silently replaced itself instead of growing into a new finalised line. Fix: detect non-continuation/non-revision partials and commit the prior text as an implicit utterance boundary. (2) displayTimestamp used HH:mm — multiple finalised lines within the same minute all displayed 'MEDIC 01:40', making them visually indistinguishable. Fix: HH:mm:ss.
…nscript behind operator's back Bug report 2026-05-07 root cause: AppState.scheduleAutoClean() ran 5 s after each silence-commit and 60 s wall-clock fallback, writing the LLM-cleaned output to state.transcriptCleaned. displayedTranscript in LiveCaptureScreen reads transcriptCleaned ?? transcript, so once auto-clean fired even once, the UI was bound to the cleaned snapshot and new utterances appended to state.transcript were not visible until the next auto-clean re-cleaned the whole window — producing the bubble-overwrite pattern (LLM merging multiple utterances into one paragraph, replacing what the operator just saw). The manual Clean transcript button remains. Auto-pass stays off until VAD-driven firing replaces the wall-clock fallback.
Adds the Granite-centered evidence ledger, packet contract, schema validators, explicit Granite text backend, unavailable Granite Speech scaffold, report critique schema, and sprint specs. Keeps Apple Speech and Apple Foundation Models as defaults while ensuring model output stays reviewable and evidence-bound.
Add a local-only Granite real-model runner, Swift artifact plumbing, manual test gate, and result documentation so the hot-seat path can be evaluated with actual MLX output while preserving the existing safety boundary. Harden candidate patch decoding and reject empty no-op patches so malformed model output cannot become review-ready.
aarzamen
pushed a commit
that referenced
this pull request
May 10, 2026
- SPRINT_1_ACCEPTANCE.md (repo root) walks v3 §13's nine criteria
with PASS / PARTIAL / DEFERRED markings + per-criterion evidence
(test names, commit refs, operator-validated metrics, screenshots).
Outcome: 7 PASS / 1 PARTIAL / 1 DEFERRED.
- 1, 2, 3, 4, 7, 8, 9: PASS.
- 5 (5-min fixture / live mic stable): DEFERRED. Two compounding
causes — encoder long-form crash (G2 §1) + live-mic mailbox
unbounded growth (G3 §2). Sprint 2 fix paths in
PRIOR_AUDIO_PATTERNS.md: chunked-encode (Top Pattern #1) +
back-pressured writer (Top Pattern #2).
- 6 (memory gauges + cap held): PARTIAL. Gauges visible + cap
held for short audio; long-form correctly triggers jetsam (the
cap is being honored — that's why the crash happens). Same
Sprint 2 fix unblocks both.
- GraniteLiveView gains a long-form warning banner per the G4
discipline-point review:
- Triggers on phase == .recording AND
(recordingDuration ≥ 60 s OR memory pressure ≠ .normal).
- Banner copy: "Long-form recording is unstable in Sprint 1 —
see G3 limitations in CLAUDE.md."
- Subtitle line surfaces the actual trigger ("duration 67s ≥ 60s
· memory pressure: warning"), and the border color escalates
warn → crit on critical pressure.
- This is preventive UX, not a bug fix — it stops the operator
walking into the live-mic memory leak without warning.
- MemoryMonitorCSVLogger wired into GraniteLiveView's recording
loop (Documents/MemoryMonitorLog.csv). Rows on record_start /
primed / per-tick / pressure_warning / pressure_critical /
record_stop / transcribe_complete / failed. This moves v3 §13
criterion 7 from FAIL to PASS — log file is non-empty after a
Granite Live session and is pullable via xcrun devicectl.
- Body wrapped in TimelineView(.periodic(by: 1.0)) so the duration
sentinel + banner re-evaluate every second even when no
MemoryMonitor reading changes.
- Packages/TCCCAudio/CLAUDE.md G4 section appended with the
acceptance-gate outcome, the PASS-list cross-reference, and the
Sprint 2 carry-over pointer to SPRINT_1_ACCEPTANCE.md.
Hardware validation: G4 polish covers the live-mic surface only;
no new heavy-runtime change. Sim build clean. Device deploy +
operator validation can land in next session — banner + CSV
logger are local UI / file I/O, both well-trodden patterns.
Tag: sprint-1-granite-speech-foundation applied to this commit on
push. Sprint 2 reads back from this tag.
Files changed: 3
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
aarzamen
pushed a commit
that referenced
this pull request
May 10, 2026
Sprint 2 spec is gated on a research pass comparing three approaches for chunked-encode in Granite Speech 4.0 1B 5-bit: 1. Single-shot encode (current Sprint 1 baseline) vs a non-encoded chunking variant that feeds audio without a per-chunk encoder pass. 2. Non-encoded chunking with very short (~3-4 s) windows. 3. Encoded chunking with longer (~10 s) windows aligned to the encoder's native context_size=200. The choice has load-bearing architectural consequences for the chunked-encode + back-pressured-writer work that closes Sprint 1's DEFERRED + PARTIAL acceptance criteria (5 + 6). Spec-blocking until the research lands; recorded in Packages/TCCCAudio/CLAUDE.md so the next session resumes from the blocker rather than re-discovering it. Spec-side resumption pointer also added: the design pass reads from SPRINT_1_ACCEPTANCE.md §"Sprint 2 carry-over" + PRIOR_AUDIO_PATTERNS.md Top Pattern #1 + this blocker section, and should spawn a research subagent (same shape as the one that produced PRIOR_AUDIO_PATTERNS.md) to fetch the upstream Granite Speech model card + any chunked-decode notes from mlx-audio-swift since v0.1.2. This commit lands AFTER the sprint-1-granite-speech-foundation tag — the tag stays at e13ecc4. Documentation drift only; no code touched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
aarzamen
pushed a commit
that referenced
this pull request
Jun 29, 2026
…witch (whole-branch review #1) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
aarzamen
pushed a commit
that referenced
this pull request
Jun 29, 2026
…vents Whole-branch review #1 (PHI-bleed): dirName carried only an integer-second stamp; End Care reuses the same casualtyId with startUnix=now and createDirectory does not throw on an existing dir, so a same-second rotation reused the just-archived dir and could resurrect its PHI as active on next launch. Add a UUID suffix to the dir name + a regression test (same-id same-second rotation must not inherit archived events). Whole-branch review #2 (audit completeness): the .rejected arm of acceptGraniteFact appended an operatorRejectedFact event but never flushed it; add persistNewEvents() so the rejection reaches disk immediately. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
aarzamen
pushed a commit
that referenced
this pull request
Jun 29, 2026
Whole-branch review finding #1 (important). persistNewEvents() reads persistedCursor, crosses two awaits (engine.newEvents, store.appendToActive), then advances the cursor — with no mutual exclusion. It is invoked from fire-and-forget Tasks (per committed ASR line via appendFinal; from GraniteReviewQueue). Under rapid dictation two invocations read the SAME cursor, slice overlapping [cursor...] ranges, and both append. BLOCK A worsened the consequence: the cursor commit changed from absolute (persistedCursor = log.events.count, which converges to the true count → dup only) to relative (persistedCursor += new.count, which overshoots → dup AND skipped events = loss on the encrypted JSONL). This restores correctness and goes further than the pre-BLOCK-A behavior: a coalescing guard lets only one drain run at a time; a call arriving mid-drain sets persistAgain and the active drain loops to pick up what it missed. Only the active invocation reads/advances the cursor, so each slice is exact — no overlap, no skip. Flag reads/writes never straddle an await, so on @mainactor they are atomic w.r.t. reentrancy. App suite 84/0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
aarzamen
pushed a commit
that referenced
this pull request
Jul 3, 2026
…eech lane, WER/recall/extraction scoring, --transcription-benchmark gate - AppleSpeechFileTranscriber actor: re-arming file-feed loop mirroring SpeechRecognizer.handleFinalResult production semantics; on-device only; 30s no-result / 60s hard-cap drain; strict-concurrency-safe (no non-Sendable capture in @sendable callback). - BenchmarkReference: slug rule + Bundle.main .txt/.json sidecar loader. - BenchStateMapper: flat [String:String] from PatientState for ExtractionScorer (moi/hr/bp/rr/spo2/classification/interventions keys). - TranscriptionBenchmarkView: --transcription-benchmark launch-arg gate; fixtures → transcribe → score WER/keyword/extraction → JSON + summary.md. BenchmarkRunResult stores werPercent/keywordRecallPercent/ extractionRecallPercent/extractionPassed/extractionTotal at construction (correction #1: computed properties are omitted by JSONEncoder, stored fields are not — JSON is self-describing without touching TCCCBench types). Memory fields use MemoryStat.availableBytes() (nil on sim, real on device). - TCCC_IOSApp: TranscriptionBenchmarkView branch between Granite and Content. - project.yml: TCCC_IOS gains TCCCBench; TCCC_IOSTests gains TCCCExtractor. - BenchStateMapperTests: 2 tests (RED→GREEN): mapper reads bp/hr/rr from a fresh standard() engine; nil state returns empty dict. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BArcT7XTFUirDSk6Xb7Keh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This branch adds the Granite hot-seat processing path and hardens the surrounding operator-safety gates. It keeps Apple Speech and Apple Foundation Models as defaults while making Granite an explicit, evidence-bound review/adjudication path rather than a direct state writer.
TranscriptStreamscaffold so the backend can be represented without silently starting audio, network, or model loads.docs/granite/specs/.Behavior Change
PatientState.Testing
xcodegen generatesucceeded.xcodebuild test -project TCCC_IOS.xcodeproj -scheme TCCC_IOS -destination 'id=DE7116A4-74E0-40EA-85C2-0D19C290BD0E' -configuration Debug CODE_SIGNING_ALLOWED=NO -skipMacroValidationpassed: 37 tests, 0 failures.swift testinPackages/TCCCKitpassed: 724 tests, 0 failures.docs/granite/specs/*.yamlanddocs/granite/SPRINT_BOARD.yaml.git diff --checkpassed.Known-But-Deferred
docs/granite/SPRINT_BOARD.yamlanddocs/superpowers/plans/2026-05-07-granite-sprint-2.md.