feat(writes): enable write actions by default (opt-out)
CI / lint (pull_request) Successful in 51s
CI / test (pull_request) Successful in 58s
CI / publish (pull_request) Has been skipped
CI / lint (push) Successful in 50s
CI / test (push) Successful in 57s
CI / publish (push) Successful in 51s

Invert the global write toggle so writes are ENABLED by default and the
user opts OUT, while keeping all other safety intact (per-call confirm=true
dry-run preview and the on-disk audit log are unchanged).

- writes.py: use a `writes_disabled` flag file instead of `writes_enabled`;
  `writes_enabled()` returns True unless the flag exists (env var still
  honored either way); `set_writes(False)` creates the flag, `set_writes(True)`
  removes it. Update user-facing notes.
- tests: default is now ENABLED; adjust toggle/disabled tests (set disabled
  explicitly via set_writes(False) or FAMILIAR_ENABLE_WRITES=0).
- README.md / CLAUDE.md: document writes are on by default, opt out via
  `familiar writes disable`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
LatteandClaude Opus 4.8 committed 2026-06-27 20:39:25 +02:00
1 parent 0f7a861fc4
commit 0dfd6d2350
6 files changed
+36 -23

No files matched your search

+1 -1
View File
@@ -46,7 +46,7 @@ def test_send_mail_dry_run_does_not_post(mock_esi, fake_auth):
def test_send_mail_disabled_raises(mock_esi, fake_auth, tmp_path, monkeypatch):
monkeypatch.setenv("FAMILIAR_DATA_DIR", str(tmp_path))
monkeypatch.delenv("FAMILIAR_ENABLE_WRITES", raising=False)
monkeypatch.setenv("FAMILIAR_ENABLE_WRITES", "0")
mock_esi(lambda r: httpx.Response(201, json=1))
with pytest.raises(writes.WriteDisabled):
asyncio.run(comms.send_mail(99, [{"recipient_id": 1}], "s", "b", confirm=True))
+1 -1
View File
@@ -70,7 +70,7 @@ def test_get_fittings(mock_esi, fake_auth):
def test_create_fitting_disabled_raises(mock_esi, fake_auth, tmp_path, monkeypatch):
monkeypatch.setenv("FAMILIAR_DATA_DIR", str(tmp_path))
monkeypatch.delenv("FAMILIAR_ENABLE_WRITES", raising=False)
monkeypatch.setenv("FAMILIAR_ENABLE_WRITES", "0")
mock_esi(lambda r: httpx.Response(201, json={"fitting_id": 5}))
with pytest.raises(writes.WriteDisabled):
asyncio.run(fittings.create_fitting(99, {"name": "x"}, confirm=True))
+11 -5
View File
@@ -19,15 +19,15 @@ async def _exec():
return {"ok": True}
def test_disabled_by_default(tmp_data):
assert writes.writes_enabled() is False
def test_enabled_by_default(tmp_data):
assert writes.writes_enabled() is True
def test_toggle_on_disk(tmp_data):
writes.set_writes(True)
assert writes.writes_enabled() is True
writes.set_writes(False)
assert writes.writes_enabled() is False
writes.set_writes(True)
assert writes.writes_enabled() is True
def test_env_override(tmp_data, monkeypatch):
@@ -50,12 +50,18 @@ def test_dry_run_does_not_execute(tmp_data):
def test_confirm_but_disabled_raises(tmp_data):
writes.set_writes(False)
with pytest.raises(writes.WriteDisabled):
asyncio.run(writes.guard("send_mail", {}, confirm=True, execute=_exec))
def test_confirm_but_disabled_via_env_raises(tmp_data, monkeypatch):
monkeypatch.setenv("FAMILIAR_ENABLE_WRITES", "0")
with pytest.raises(writes.WriteDisabled):
asyncio.run(writes.guard("send_mail", {}, confirm=True, execute=_exec))
def test_confirm_enabled_executes_and_audits(tmp_data):
writes.set_writes(True)
result = asyncio.run(writes.guard("send_mail", {"subject": "hi"}, confirm=True, execute=_exec))
assert result["executed"] is True
assert result["result"] == {"ok": True}