on-device-privacy: convert "metadata persists, audio doesn't" to a check - #69
Merged
missingbulb merged 1 commit intoJul 29, 2026
Merged
Conversation
RULES.md's audio-persistence promise was always-testable but only ever enforced as prose. Two prior sweeps (#51, #54) left it as prose, reasoning that "does this new field carry audio" is a judgment about meaning. This sidesteps that framing entirely: instead of classifying fields, it bans the known audio-file-writing APIs directly (AVAudioFile(forWriting:), AVAudioRecorder, ExtAudioFileCreateWithURL, AudioFileCreateWithURL) in mac/Sources/**.swift — the same absolute-ban shape no-network-client already uses for egress. AVAudioFile(forReading:) is deliberately excluded (loading a bundled sound asset isn't persistence); a fixture pins that false alarm. Refs #42. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013T5fxiwvVP7REPbFNL174j
This was referenced Jul 29, 2026
missingbulb
deleted the
claude/prose-to-checks-audio-persistence-d20260729
branch
July 29, 2026 13:34
This was referenced Jul 30, 2026
missingbulb
added a commit
that referenced
this pull request
Aug 7, 2026
Three lessons from the 2026-07-26..08-02 window, into the laughcounter local pack as prose. #74/#75: diagnosing the installTap crash stalled on "which binary is installed?" — the menu said only "LaughCounter", and several distinct builds all reported 0.2.1, because the release workflow keys its Release on v<version> from Info.plist, so a merge that leaves CFBundleShortVersionString alone refreshes the same Release behind the latest/download link. Durable part: show version and build from Bundle.main (not a source constant that could disagree with the DMG), and bump per distinguishable build. #56: the scheduler ran green nightly while silently skipping baselining ("no vendored mount (no stamp)") because the vendored loadConfig dropped the `claudinite` key it had just validated. Durable part: a job whose success and whose no-op look identical from outside is telling you nothing — read the skip line; and a bug inside the mechanism that updates itself has to be fixed out of band. #34: claudinite-isolation fired on CLAUDE.md's mount path, which carried nothing a reader could act on. Durable part: before adding an `accept`, delete the flagged text and see whether anything actionable went with it — an accept is for a crossing that must exist. Nothing new from the mac window (#59, #73, #77, #78, #87, #100, #101, #108): dev/procedures/mac-audio-lifecycle.md already records the engine-per-start rule, the inputFormat-vs-outputFormat trap, the aggregate churn, the three-state health reporting, the witnessed-arrival settle rule and the observation-gap rule in full. #55, #69, #84 and #99 are already carried by this pack's existing prose and the on-device-privacy checks. #32's "prove the check is live" is the see-it-fail discipline the canon owns. Conversation-logs half: the 2026-08-01 logs are the #108 session (fully covered above) and unattended task runs; no new friction lesson. No retention_days configured, so no prune. Refs #113. Claude-Session: https://claude.ai/code/session_01G52dxZvLCxyZJJnvLrYcQs Co-authored-by: Claude <noreply@anthropic.com>
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.
The
prose-to-checks-sweeprun over.claudinite/local/packs(the only local pack here ison-device-privacy). OneRULES.mdrule that two prior sweeps (#51, #54) left as prose is now a check.Tracker: #42
Converted
"No audio is stored at all" →
on-device-privacy/no-audio-persistence(blocking)Both prior sweeps considered this rule and declined it, reasoning that "does this new persisted field carry audio" is a judgment about a field's meaning, not a static signature. That framing is correct for classifying arbitrary new fields — but it isn't the only way to hold the rule. Instead of classifying fields, this check bans the known audio-file-writing APIs directly in
mac/Sources/**.swift:AVAudioFile(forWriting:),AVAudioRecorder,ExtAudioFileCreateWithURL,AudioFileCreateWithURL. That's the same absolute-ban shapeno-network-clientalready uses for egress — a fixed list of API tokens, not an attempt to reason about what a hypothetical future field "means."AVAudioFile(forReading:)is deliberately excluded from the ban — loading a bundled sound asset (e.g. a custom chime) isn't persistence, and a fixture pins that specific non-finding so the check can't drift into banning it by accident.This holds the promise for v1 (
docs/DESIGN-AND-TRADEOFFS.md§6: "in v1, not stored at all"). The roadmap's v3 deliberately adds a rolling audio buffer for on-device retraining — landing that is a product decision, so it updates this check (and the mic usage strings) in the same PR, exactly asno-network-clientalready expects of any future egress decision.See-it-fail
node --test .claudinite/local/packs/on-device-privacy/pack.test.mjs— 7/7 pass (3 new): fires onAVAudioFile(forWriting:)andAVAudioRecorder, stays quiet on a read-onlyAVAudioFile(forReading:)load, and stays quiet on the real capture path (AudioHub.swift,VoiceCommand.swift,Store.swift,LaughDetector.swift,AppLog.swift).run-active-pack-rules.mjs,check_the_world.mjs, andcheck_the_work.mjsall exit 0 with the new rule registered.Left as prose
FileManagerwrite targets back to its declaration, not just grepping a token; left as prose per the skill's guidance rather than shipping a check that can't be made confident.no-network-clientcheck (it bansimport Networkoutright, which already rules out a listener too); converting either as a separate rule would just duplicate it.Prose trimmed
RULES.md's audio-persistence paragraph drops the mechanical claim, keeps the rationale, and points at the check plus the v3 caveat.README.md's enforcement table moves that row off "prose".Surface
One new rule module, its
pack.mjsline, its fixtures, and the trimmed prose (plus the README table). Nothing outside.claudinite/local/packs/on-device-privacy/.Left open for review, per this task's
open-prceiling — a new blocking check is exactly the thing that shouldn't self-merge.Generated by Claude Code