Skip to content

Claudinite growth: convert two on-device-privacy prose rules to checks - #54

Closed
missingbulb wants to merge 1 commit into
mainfrom
claude/prose-to-checks-privacy-sweep
Closed

Claudinite growth: convert two on-device-privacy prose rules to checks#54
missingbulb wants to merge 1 commit into
mainfrom
claude/prose-to-checks-privacy-sweep

Conversation

@missingbulb

Copy link
Copy Markdown
Owner

The prose-to-checks-sweep run over .claudinite/local/packs (the only local pack here is on-device-privacy). Two of its RULES.md rules were always-testable but enforced only by prose. Both are checks now; the other three prose rules stay prose (see below).

Tracker: #42

Converted

"Everything lives in one deletable directory." → on-device-privacy/single-home-directory (blocking)

Scans laughcounter/**.py for home-anchored path literals (Path.home() / "…", quoted "~/…") that land outside ~/.laughcounter, and mac/Sources/**.swift for a Foundation standard directory other than Application Support, an Application Support base never narrowed with appendingPathComponent("LaughCounter"), and NSHomeDirectory().

~/Library/LaunchAgents is exempt by name and the reason is in the check: launchd mandates that location, and laughcounter service only prints the plist for the user to redirect.

"Any new mutating endpoint keeps that guard." → on-device-privacy/json-content-type-guard (blocking)

Cuts each do_POST / do_PUT / do_PATCH / do_DELETE body out by indentation and requires an application/json content-type check inside it, positioned ahead of any body read. Parsing the handler rather than grepping the file is what makes it confident — application/json appears throughout dashboard.py as a response content type, and a whole-file grep would accept those.

See-it-fail

Beyond the fixtures, each check was run against the real sources with a realistic regression applied, and each produced exactly one finding:

Mutation to real source Finding
config.py home → ~/.cache/laughcounter home-anchored path lands in ~/.cache
Store.swift drops the LaughCounter narrowing Application Support base not narrowed
Store.swift.cachesDirectory state placed in .cachesDirectory
dashboard.py content-type guard deleted do_POST never requires application/json
dashboard.py body read hoisted above the guard do_POST reads the body before checking the type
unguarded do_DELETE added do_DELETE never requires application/json

node --test .claudinite/local/packs/on-device-privacy/pack.test.mjs — 11/11 pass. check_the_world and check_the_work both exit 0.

Left as prose

  • Metadata persists, audio doesn't — "does this new persisted field carry audio" is a judgment about a field's meaning, not a static signature.
  • The model download is the only accepted egress — the negative half is already no-network-client; the positive half ("is this new egress acceptable") is a decision, not a condition.
  • The disclosure must stay true — the enforceable version (the usage strings exist) is a weaker, different rule than the one the prose states (they change in the same commit as the behaviour they describe), which leaves no artifact to scan.

Prose trimmed

RULES.md's two converted paragraphs drop the mechanical statement and keep only the rationale the checks can't hold; README.md's enforcement table moves both rows off "prose" and gains the new content-type row.


Generated by Claude Code

#42)

The prose-to-checks sweep over `.claudinite/local/packs`. Two RULES.md rules
were always-testable but enforced only by prose; both are checks now, each
with a fixture that fires on a violation and stays quiet on the real tree.

- `on-device-privacy/single-home-directory` — "everything lives in one
  deletable directory". Scans `laughcounter/**.py` for home-anchored path
  literals (`Path.home() / "…"`, `"~/…"`) that land outside `~/.laughcounter`,
  and `mac/Sources/**.swift` for Foundation standard directories other than
  Application Support, an Application Support base never narrowed to
  `LaughCounter/`, and `NSHomeDirectory()`. `~/Library/LaunchAgents` is exempt
  by name — launchd mandates that location and the app only prints the plist.

- `on-device-privacy/json-content-type-guard` — "any new mutating endpoint
  keeps that guard". Cuts each `do_POST`/`do_PUT`/`do_PATCH`/`do_DELETE` body
  out by indentation and requires an `application/json` content-type check
  inside it, ahead of any body read. Parsing the handler rather than grepping
  the file is what makes it confident: `application/json` appears all over
  dashboard.py as a *response* type.

Seen to fail against the real sources, not just the fixtures: moving the
Python home to `~/.cache`, dropping Store.swift's `LaughCounter` narrowing,
switching it to `.cachesDirectory`, deleting dashboard.py's content-type
guard, hoisting the body read above it, and adding an unguarded `do_DELETE`
each produce exactly one finding.

RULES.md's two paragraphs are trimmed to the rationale the checks can't hold;
README.md's enforcement table moves both rows off "prose".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xuv7SEg18SGzavEFRgMhcu

Copy link
Copy Markdown
Owner Author

Superseded by #55, which merges this PR and #51 — two independent runs of the same prose-to-checks sweep over on-device-privacy.

#55 keeps this PR's Swift coverage on single-home-directory (Foundation standard directories, the Application Support narrowing, NSHomeDirectory()) and its guard-ordering rule on json-content-type-guard. From #51 it takes the tighter content-type detection — anchoring on .headers.get("Content-Type") rather than any "Content-Type" literal, so a response-side send_header can't pass as a guard — and the Path.home() test that fires on Path.home() / some_var, not only on a literal join.

Closing in favour of #55.


Generated by Claude Code

missingbulb added a commit that referenced this pull request Jul 29, 2026
…eck (#69)

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.


Claude-Session: https://claude.ai/code/session_013T5fxiwvVP7REPbFNL174j

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants