feat: link effect to specific git commit for provenance

- Capture effects repo commit hash at render time
- Store effects_commit in run record
- Effect URLs now link to exact commit, not main branch
- Include commit in raw JSON provenance

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gilesb
2026-01-07 14:29:40 +00:00
parent 5f7b6c3031
commit 0c7e43e069
2 changed files with 48 additions and 2 deletions

View File

@@ -89,6 +89,7 @@ class RunStatus(BaseModel):
output_hash: Optional[str] = None
error: Optional[str] = None
celery_task_id: Optional[str] = None
effects_commit: Optional[str] = None
def file_hash(path: Path) -> str:
@@ -268,6 +269,11 @@ async def get_run(run_id: str):
run.completed_at = datetime.now(timezone.utc).isoformat()
run.output_hash = result.get("output", {}).get("content_hash")
# Extract effects commit from provenance
effects = result.get("effects", [])
if effects:
run.effects_commit = effects[0].get("repo_commit")
# Cache the output
output_path = Path(result.get("output", {}).get("local_path", ""))
if output_path.exists():
@@ -641,6 +647,10 @@ async def ui_detail_page(run_id: str):
run.status = "completed"
run.completed_at = datetime.now(timezone.utc).isoformat()
run.output_hash = result.get("output", {}).get("content_hash")
# Extract effects commit
effects = result.get("effects", [])
if effects:
run.effects_commit = effects[0].get("repo_commit")
output_path = Path(result.get("output", {}).get("local_path", ""))
if output_path.exists():
cache_file(output_path)
@@ -649,7 +659,11 @@ async def ui_detail_page(run_id: str):
run.error = str(task.result)
save_run(run)
effect_url = f"https://git.rose-ash.com/art-dag/effects/src/branch/main/{run.recipe}"
# Build effect URL - use commit hash if available
if run.effects_commit and run.effects_commit != "unknown":
effect_url = f"https://git.rose-ash.com/art-dag/effects/src/commit/{run.effects_commit}/{run.recipe}"
else:
effect_url = f"https://git.rose-ash.com/art-dag/effects/src/branch/main/{run.recipe}"
status_class = run.status
html = f"""
@@ -769,6 +783,7 @@ async def ui_detail_page(run_id: str):
"run_id": run.run_id,
"status": run.status,
"recipe": run.recipe,
"effects_commit": run.effects_commit,
"effect_url": effect_url,
"inputs": run.inputs,
"output_hash": run.output_hash,
@@ -805,6 +820,10 @@ async def ui_run_partial(run_id: str):
run.status = "completed"
run.completed_at = datetime.now(timezone.utc).isoformat()
run.output_hash = result.get("output", {}).get("content_hash")
# Extract effects commit
effects = result.get("effects", [])
if effects:
run.effects_commit = effects[0].get("repo_commit")
output_path = Path(result.get("output", {}).get("local_path", ""))
if output_path.exists():
cache_file(output_path)

View File

@@ -7,6 +7,7 @@ Distributed rendering tasks for the Art DAG system.
import hashlib
import json
import os
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path
@@ -16,6 +17,24 @@ from celery_app import app
# Add effects to path (use env var in Docker, fallback to home dir locally)
EFFECTS_PATH = Path(os.environ.get("EFFECTS_PATH", str(Path.home() / "artdag-effects")))
def get_effects_commit() -> str:
"""Get current git commit hash of effects repo."""
try:
result = subprocess.run(
["git", "rev-parse", "HEAD"],
cwd=EFFECTS_PATH,
capture_output=True,
text=True
)
if result.returncode == 0:
return result.stdout.strip()
except Exception:
pass
return "unknown"
sys.path.insert(0, str(EFFECTS_PATH / "dog"))
@@ -106,6 +125,9 @@ def render_effect(self, input_hash: str, effect_name: str, output_name: str) ->
if output_hash != expected_hash:
raise ValueError(f"Output hash mismatch: expected {expected_hash}, got {output_hash}")
# Get effects repo commit
effects_commit = get_effects_commit()
# Build provenance
provenance = {
"task_id": self.request.id,
@@ -120,7 +142,12 @@ def render_effect(self, input_hash: str, effect_name: str, output_name: str) ->
{"content_hash": input_hash}
],
"effects": [
{"name": f"effect:{effect_name}", "content_hash": REGISTRY[f"effect:{effect_name}"]["hash"]}
{
"name": f"effect:{effect_name}",
"content_hash": REGISTRY[f"effect:{effect_name}"]["hash"],
"repo_commit": effects_commit,
"repo_url": f"https://git.rose-ash.com/art-dag/effects/src/commit/{effects_commit}/{effect_name}"
}
],
"infrastructure": {
"software": {"name": "infra:artdag", "content_hash": REGISTRY["infra:artdag"]["hash"]},