060fa408a2
§14.1 richer landing, §14.2 /philosophy route (disk-backed), §14.3 persistent About link. /settings/notifications surfaces Slice 6's preferences/quiet-hours/mute/watches endpoints. /admin home base consolidates role management, the §6.2 write-mute, the audit-log viewer, the permission-events log, and the §13.2 graduation queue. Backend: backend/app/philosophy.py, backend/app/api_admin.py (seven admin endpoints + user-search), GET /api/users/me/notification-mutes. Frontend: Landing.jsx (deck), Philosophy.jsx, NotificationSettings.jsx, Admin.jsx, App.jsx routing for the chrome surfaces. Tests: backend/tests/test_chrome_vertical.py — 13 cases. Full suite 75/75 green. Spec corrections: §14.2 (PHILOSOPHY.md source is a deployment-time decision), §17 (admin block extended to name the seven new endpoints + user-search and notification-mutes read). §19.1 rewritten for Slice 8 hardening; §19.2 grew four candidates (owner succession, mute-from-actor, the "Following since <date>" disclosure, audit-log row prose). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
67 lines
2.2 KiB
Python
67 lines
2.2 KiB
Python
"""§14.2 philosophy source.
|
|
|
|
The spec names PHILOSOPHY.md as the body the `/philosophy` route renders,
|
|
"sourced from the meta repo's main branch, cached and refreshed on the
|
|
same cadence as RFC bodies (§4)." Slice 7 picks the disk-first shape:
|
|
the file is checked into the app repo alongside SPEC.md, since it is
|
|
the framework's design document rather than an RFC entry, and reading
|
|
it from disk at process start (with a periodic re-read for hot edits)
|
|
puts the framework's mission in front of the reader without an extra
|
|
Gitea round-trip on the first hit.
|
|
|
|
If a downstream deployment hosts PHILOSOPHY.md in the meta repo
|
|
instead, the `PHILOSOPHY_PATH` env var can point at a working-tree
|
|
clone or a sync target; the loader does not care which.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
import threading
|
|
from pathlib import Path
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
_DEFAULT_PATH = Path(__file__).resolve().parents[2] / "PHILOSOPHY.md"
|
|
|
|
_lock = threading.Lock()
|
|
_cache: dict | None = None
|
|
|
|
|
|
def _resolved_path() -> Path:
|
|
override = os.environ.get("PHILOSOPHY_PATH", "").strip()
|
|
if override:
|
|
return Path(override).expanduser().resolve()
|
|
return _DEFAULT_PATH
|
|
|
|
|
|
def load(force: bool = False) -> dict:
|
|
"""Return the cached `{body, path, mtime}` payload, reading from disk
|
|
on first call or when `force=True`. The reconciler's sweep can call
|
|
this with `force=True` to pick up out-of-band edits.
|
|
"""
|
|
global _cache
|
|
with _lock:
|
|
if _cache is not None and not force:
|
|
return _cache
|
|
path = _resolved_path()
|
|
try:
|
|
text = path.read_text(encoding="utf-8")
|
|
mtime = path.stat().st_mtime
|
|
except FileNotFoundError:
|
|
log.warning("PHILOSOPHY.md not found at %s — serving placeholder", path)
|
|
text = (
|
|
"# PHILOSOPHY.md not found\n\n"
|
|
"The deployment is missing its philosophy document. Set "
|
|
"PHILOSOPHY_PATH or place PHILOSOPHY.md at the project root."
|
|
)
|
|
mtime = 0.0
|
|
_cache = {"body": text, "path": str(path), "mtime": mtime}
|
|
return _cache
|
|
|
|
|
|
def refresh() -> dict:
|
|
"""Force-reread from disk. Returns the new payload."""
|
|
return load(force=True)
|