backlot phase 0 review fixes: fail-closed gates, atomic checkpoint writes, event attribution hardening

- gate = manifest OR caller (stricter wins); unknown pipeline_type now
  raises instead of silently disabling enforcement; corrupt manifest logs
  and falls back; misleading diagnostic fixed
- write_checkpoint backfills pipeline_type from project.json marker so
  omitting the kwarg can't bypass gates
- checkpoint writes are atomic (temp + os.replace); history archiving is
  copy-based and best-effort (Windows open-file safe)
- manifest loads cached (load_pipeline_readonly); stage gate lookup moved
  to pipeline_loader.get_stage_human_approval_default; PROJECTS_DIR unified
  in lib/paths.py
- events: containment + root-normalization for explicit project dirs, no
  ghost-project mkdir, 0.0 cost preserved, nested-call depth tag,
  wrapper simplified
- documentary-montage edit-director gate footer (was missed); AGENT_GUIDE
  no longer claims edit/compose always auto-proceed
This commit is contained in:
calesthio
2026-07-01 23:24:11 -07:00
parent 722491d732
commit 514d0faf37
7 changed files with 215 additions and 88 deletions

View File

@@ -21,11 +21,31 @@ SCHEMA_PATH = (
)
from functools import lru_cache
@lru_cache(maxsize=1)
def _load_manifest_schema() -> dict:
with open(SCHEMA_PATH) as f:
return json.load(f)
@lru_cache(maxsize=64)
def _load_pipeline_cached(name: str, defs_dir_key: str) -> dict[str, Any]:
"""Cached manifest load. Treat the returned dict as READ-ONLY."""
return load_pipeline(name, Path(defs_dir_key) if defs_dir_key else None)
def load_pipeline_readonly(name: str, defs_dir: Optional[Path] = None) -> dict[str, Any]:
"""Load a manifest through a cache. The result MUST NOT be mutated.
Manifests are immutable within a run; hot paths (gate checks on every
checkpoint write, board state derivation) should use this instead of
re-parsing YAML + re-validating the schema each call.
"""
return _load_pipeline_cached(name, str(defs_dir) if defs_dir else "")
def load_pipeline(name: str, defs_dir: Optional[Path] = None) -> dict[str, Any]:
"""Load and validate a pipeline manifest by name.
@@ -150,6 +170,18 @@ def get_stage_skill(manifest: dict, stage_name: str) -> Optional[str]:
return None
def get_stage_human_approval_default(manifest: dict, stage_name: str) -> Optional[bool]:
"""Whether a stage gates on human approval. None if the stage isn't declared.
This is the single lookup used by gate enforcement (lib/checkpoint.py)
and the Backlot board — keep them reading the same field the same way.
"""
for stage in manifest["stages"]:
if stage["name"] == stage_name:
return bool(stage.get("human_approval_default", False))
return None
def get_stage_review_focus(manifest: dict, stage_name: str) -> list[str]:
"""Get the review focus items for a stage."""
for stage in manifest["stages"]: