feat(privacy-host): add signed native calendar contacts and reminders host

Add the owner-only AF_UNIX Reyna CLI privacy host, strict signed-app installation, and typed native routing for Calendar, Contacts, and Reminders.\n\nAdd bounded system-status paths and config-only direct local-service wrappers. Preserve MacMiniMCP pending explicit cutover approval.\n\nApple Notes is intentionally deferred: no native Notes operations, Apple Events declaration, or Automation helper are included; legacy Notes handling remains untouched.
This commit is contained in:
Adolfo Reyna
2026-08-03 20:27:54 -04:00
parent 6e2117188e
commit 9fd04b0ce4
56 changed files with 14239 additions and 50 deletions
+268
View File
@@ -0,0 +1,268 @@
"""Tests for Notes deferred status — no Notes integration may exist.
Assert:
- no `notes.` protocol ops
- no native_notes wrappers
- no CLI Notes authorization/subcommands
- AppleEvents usage key forbidden and absent from plist/app policy
- no AppKit link/import
- docs correctly say deferred + legacy untouched
"""
from __future__ import annotations
import plistlib
from pathlib import Path
import re
REPO = Path(__file__).resolve().parents[1]
def test_no_notes_protocol_ops_in_privacy_contract():
from reyna_cli.privacy_contract import ALLOWED_OPERATIONS, _COMMAND_TO_OPERATION
# No operation may start with notes.
for op in ALLOWED_OPERATIONS.keys():
assert not op.startswith("notes."), f"forbidden notes op {op} still present — Notes is deferred"
# Explicit strings must not appear
forbidden_ops = {"notes.list", "notes.read", "notes.create", "notes.request_access"}
for fo in forbidden_ops:
assert fo not in ALLOWED_OPERATIONS, f"forbidden {fo} present"
for cmd, op in _COMMAND_TO_OPERATION.items():
assert not op.startswith("notes."), f"command {cmd} maps to forbidden notes op {op}"
assert "notes" not in cmd.lower() or "notes" in cmd.lower() and False is False # allow key detection via op only
# also forbid notes_* commands mapping
assert not cmd.startswith("notes_"), f"forbidden notes command {cmd}"
def test_no_native_notes_wrappers():
src = (REPO / "src" / "reyna_cli" / "privacy_host.py").read_text()
assert "native_notes" not in src, "native_notes wrappers must be removed — Notes deferred"
assert "NotesProvider" not in src
assert "NotesAuthorization" not in src
# generic notes field as parameter name for calendar/reminders is allowed,
# but operation names notes.list etc are forbidden — already checked by string search above
# Ensure no wrapper defs remain
for name in ["native_notes_list", "native_notes_read", "native_notes_create", "native_notes_request_access"]:
assert name not in src
def test_no_cli_notes_subcommands():
cli_src = (REPO / "src" / "reyna_cli" / "cli.py").read_text()
# macmini notes subcommands must be gone
assert "macmini_notes" not in cli_src, "macmini_notes commands must be removed"
# CLI must not register a notes typer under macmini
# Check that macmini help mentions notes is gone — we test via source: no notes app registration
# Allow word 'notes' as parameter name (calendar notes, reminder notes) — but not as subcommand registration
# So forbid 'notes' app creation for macmini
# Look for macmini_notes typed list/read/create defs
assert "def macmini_notes" not in cli_src
# Privacy-host notes-authorize must be gone
assert "notes-authorize" not in cli_src
assert "notes_authorize" not in cli_src
# native_notes usage in cli must be gone
assert "native_notes" not in cli_src
# Ensure NotesProvider strings absent
assert "NotesProvider" not in cli_src
def test_appleevents_usage_forbidden():
from reyna_cli import app_bundle as ab
d = ab.build_app_bundle_info_plist_dict()
assert "NSAppleEventsUsageDescription" not in d, "AppleEvents usage must be forbidden — Notes deferred"
# Also check Xcode source plist
xcode_plist = REPO / "native" / "ReynaCLIHost" / "ReynaCLIHost" / "Info.plist"
assert xcode_plist.exists()
with open(xcode_plist, "rb") as f:
xd = plistlib.load(f)
assert "NSAppleEventsUsageDescription" not in xd, "Xcode Info.plist must not contain AppleEvents"
# App bundle policy: forbidden key must be flagged
# Validate that forbidden set includes only Calendar, Contacts, Reminders
allowed = {
"NSCalendarsFullAccessUsageDescription",
"NSCalendarsWriteOnlyAccessUsageDescription",
"NSCalendarsUsageDescription",
"NSContactsUsageDescription",
"NSRemindersFullAccessUsageDescription",
}
for k in d.keys():
if k.startswith("NS") and "UsageDescription" in k:
assert k in allowed, f"unexpected usage key {k} — only calendar/contacts/reminders allowed (Notes deferred)"
def test_app_bundle_validator_forbids_appleevents():
from reyna_cli import app_bundle as ab
import tempfile
repo_root = Path(tempfile.mkdtemp()) / "repo"
bundle = repo_root / "native" / "ReynaCLIHost" / "dist" / "Reyna CLI.app"
contents = bundle / "Contents"
macos = contents / "MacOS"
macos.mkdir(parents=True)
exe = macos / "ReynaCLIHost"
exe.write_bytes(b"binary")
exe.chmod(0o755)
class Proc:
returncode = 0
stdout = ""
stderr = "TeamIdentifier=TEAM123\nAuthority=Apple Development: Foo (TEAM123)\n"
def runner_ok(args, cwd=None, **kwargs):
return Proc()
good = ab.build_app_bundle_info_plist_dict()
with open(contents / "Info.plist", "wb") as f:
plistlib.dump(good, f)
res_ok = ab.validate_app_bundle(repo_root=repo_root, runner=runner_ok)
assert res_ok["ok"] is True, f"should accept plist without AppleEvents: {res_ok['errors']}"
bad = dict(good)
bad["NSAppleEventsUsageDescription"] = "Allow automation"
with open(contents / "Info.plist", "wb") as f:
plistlib.dump(bad, f)
res_bad = ab.validate_app_bundle(repo_root=repo_root, runner=runner_ok)
assert res_bad["ok"] is False
assert any("appleevents" in e.lower() or "unexpected" in e.lower() or "forbidden" in e.lower() for e in res_bad["errors"])
def test_no_appkit_link_or_import():
# AppMain and AppEntry must not import AppKit, must not contain AppleEvents
app_main = (REPO / "native" / "ReynaCLIHost" / "ReynaCLIHost" / "AppMain.swift").read_text()
app_entry = (REPO / "native" / "ReynaCLIHost" / "Sources" / "ReynaCLIHostCore" / "AppEntry.swift").read_text()
for txt, label in [(app_main, "AppMain"), (app_entry, "AppEntry")]:
assert "AppKit" not in txt, f"{label} must not import AppKit — Notes deferred, headless"
assert "NSAppleScript" not in txt
assert "NSAppleEvent" not in txt
assert "AppleEvents" not in txt
# Package.swift must not link AppKit
pkg = (REPO / "native" / "ReynaCLIHost" / "Package.swift").read_text()
assert "AppKit" not in pkg
# pbxproj must not contain AppKit, NotesProvider, NotesAuthorization, ForegroundApp, AppleEventsUsageDescription
pbx = (REPO / "native" / "ReynaCLIHost" / "ReynaCLIHost.xcodeproj" / "project.pbxproj").read_text()
assert "AppKit.framework" not in pbx
assert "NotesProvider.swift" not in pbx, "NotesProvider must not be in Xcode project — deferred"
assert "NotesAuthorizationProvider.swift" not in pbx
assert "ForegroundApp" not in pbx
assert "ForegroundAuthorization" not in pbx
assert "NSAppleEventsUsageDescription" not in pbx
# Protocol.swift must not reference notes operations
proto = (REPO / "native" / "ReynaCLIHost" / "Sources" / "ReynaCLIHostCore" / "Protocol.swift").read_text()
assert "notes.list" not in proto.lower()
assert "notes.read" not in proto.lower()
assert "notes.create" not in proto.lower()
assert "notes.request_access" not in proto.lower()
# Generic notes text field for calendar/reminders is allowed, but operation "notes." must not exist
# Check for NotesProvider types
assert "NotesProvider" not in proto
assert "NotesAuthorization" not in proto
assert "NoteListItem" not in proto and "NoteDetailItem" not in proto, "Notes data models must be removed"
def test_no_notes_in_swift_sources():
core_dir = REPO / "native" / "ReynaCLIHost" / "Sources" / "ReynaCLIHostCore"
for p in core_dir.glob("*.swift"):
txt = p.read_text()
low = txt.lower()
# forbid notes. ops
assert "notes.list" not in low
assert "notes.read" not in low
assert "notes.create" not in low
assert "notes.request_access" not in low
# forbid NSAppleEvents and Notes-type providers
assert "nsappleeventsusagedescription" not in low
assert "NotesProvider" not in txt
assert "NotesAuthorization" not in txt
def test_swift_tests_deleted():
tests_dir = REPO / "native" / "ReynaCLIHost" / "Tests" / "ReynaCLIHostTests"
forbidden = ["NotesAuthorizationTests.swift", "NotesOperationsTests.swift", "ForegroundAuthorizationTests.swift"]
for name in forbidden:
assert not (tests_dir / name).exists(), f"{name} must be deleted"
def test_python_notes_tests_deleted():
assert not (REPO / "tests" / "test_foreground_notes_ls_authorize.py").exists()
assert not (REPO / "tests" / "test_notes_authorization_boundary.py").exists()
def test_docs_deferred_legacy_untouched():
matrix_path = REPO / "docs" / "remaining-coverage-matrix.md"
assert matrix_path.exists()
txt = matrix_path.read_text()
low = txt.lower()
# Must say Notes deferred
assert "notes" in low
assert "deferred" in low, "docs must say Notes deferred"
# Must say legacy untouched
assert "legacy" in low
assert "untouched" in low, "docs must say legacy untouched"
# Must not say Notes belongs inside host as active work — should be in deferred section
# Ensure no active plan to add NotesProvider now
# Allow legacy mention but not as A TODO — check that if Notes is mentioned as A belongs, it's qualified as deferred
# Simplest: ensure the doc contains explicit deferred banner
assert "deferred" in txt, "doc must contain deferred word"
# Ensure no forbidden old instructions about adding NotesProvider as immediate work without deferred qualifier
# The deferred table should list Notes as deferred, not as Done A
# We'll just ensure the word deferred appears near Notes line
for line in txt.splitlines():
if "notes" in line.lower() and ("list/read/create" in line.lower() or "notes.js" in line.lower()):
# in that row, must mention deferred or C or out of scope
assert "deferred" in line.lower() or "untouched" in txt.lower(), f"Notes row must mention deferred: {line}"
def test_no_foreground_notes_references():
# Search all source/test tree for forbidden patterns — but allow generic param named notes (calendar/reminder text)
forbidden_exact = [
"NotesProvider",
"NotesAuthorization",
"ForegroundApp",
"foreground-notes",
"NSAppleEventsUsageDescription",
"notes.request_access",
"native_notes",
]
exclude_dirs = {".venv", "__pycache__", ".git", "build", ".build", "DerivedData", "dist"}
for pattern in forbidden_exact:
for path in REPO.rglob("*"):
if not path.is_file():
continue
# skip excluded
if any(part in exclude_dirs for part in path.parts):
continue
# skip backup
if "backup" in path.parts:
continue
# Only check relevant extensions
if path.suffix not in {".py", ".swift", ".plist", ".md", ".pbxproj", ".toml", ".yaml", ".yml"}:
# also check .xcodeproj is dir, pbxproj covered
if path.name != "project.pbxproj":
continue
# Skip this test file itself if pattern is mentioned in test strings — we need to allow self-reference check for patterns inside this file?
# For this file we will skip self to avoid false positive on literal search
if path.name == "test_notes_deferred.py" or "tests" in path.parts:
continue
# Skip docs remaining-coverage — allowed to mention pattern but must also say deferred; we already validated
# However per task: eliminate only actual Notes integration references (do not remove generic calendar/reminder text fields named notes)
# For forbidden patterns search, we strictly forbid integration references in source/test, not docs describing deferred
if "docs/" in str(path) and pattern == "NSAppleEventsUsageDescription":
continue
if path.name == "app_bundle.py" and pattern == "NSAppleEventsUsageDescription":
continue
try:
txt = path.read_text(errors="ignore")
except Exception:
continue
if pattern in txt:
# Allow generic reminder/calendar 'notes' param already excluded by exact list above — so any hit is real integration
# But also allow mention in backup
if "test_notes_deferred" in str(path):
continue
raise AssertionError(f"forbidden pattern '{pattern}' found in {path}")