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:
21
server.py
21
server.py
@@ -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)
|
||||
|
||||
29
tasks.py
29
tasks.py
@@ -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"]},
|
||||
|
||||
Reference in New Issue
Block a user