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
|
output_hash: Optional[str] = None
|
||||||
error: Optional[str] = None
|
error: Optional[str] = None
|
||||||
celery_task_id: Optional[str] = None
|
celery_task_id: Optional[str] = None
|
||||||
|
effects_commit: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
def file_hash(path: Path) -> str:
|
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.completed_at = datetime.now(timezone.utc).isoformat()
|
||||||
run.output_hash = result.get("output", {}).get("content_hash")
|
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
|
# Cache the output
|
||||||
output_path = Path(result.get("output", {}).get("local_path", ""))
|
output_path = Path(result.get("output", {}).get("local_path", ""))
|
||||||
if output_path.exists():
|
if output_path.exists():
|
||||||
@@ -641,6 +647,10 @@ async def ui_detail_page(run_id: str):
|
|||||||
run.status = "completed"
|
run.status = "completed"
|
||||||
run.completed_at = datetime.now(timezone.utc).isoformat()
|
run.completed_at = datetime.now(timezone.utc).isoformat()
|
||||||
run.output_hash = result.get("output", {}).get("content_hash")
|
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", ""))
|
output_path = Path(result.get("output", {}).get("local_path", ""))
|
||||||
if output_path.exists():
|
if output_path.exists():
|
||||||
cache_file(output_path)
|
cache_file(output_path)
|
||||||
@@ -649,7 +659,11 @@ async def ui_detail_page(run_id: str):
|
|||||||
run.error = str(task.result)
|
run.error = str(task.result)
|
||||||
save_run(run)
|
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
|
status_class = run.status
|
||||||
|
|
||||||
html = f"""
|
html = f"""
|
||||||
@@ -769,6 +783,7 @@ async def ui_detail_page(run_id: str):
|
|||||||
"run_id": run.run_id,
|
"run_id": run.run_id,
|
||||||
"status": run.status,
|
"status": run.status,
|
||||||
"recipe": run.recipe,
|
"recipe": run.recipe,
|
||||||
|
"effects_commit": run.effects_commit,
|
||||||
"effect_url": effect_url,
|
"effect_url": effect_url,
|
||||||
"inputs": run.inputs,
|
"inputs": run.inputs,
|
||||||
"output_hash": run.output_hash,
|
"output_hash": run.output_hash,
|
||||||
@@ -805,6 +820,10 @@ async def ui_run_partial(run_id: str):
|
|||||||
run.status = "completed"
|
run.status = "completed"
|
||||||
run.completed_at = datetime.now(timezone.utc).isoformat()
|
run.completed_at = datetime.now(timezone.utc).isoformat()
|
||||||
run.output_hash = result.get("output", {}).get("content_hash")
|
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", ""))
|
output_path = Path(result.get("output", {}).get("local_path", ""))
|
||||||
if output_path.exists():
|
if output_path.exists():
|
||||||
cache_file(output_path)
|
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 hashlib
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from pathlib import Path
|
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)
|
# 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")))
|
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"))
|
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:
|
if output_hash != expected_hash:
|
||||||
raise ValueError(f"Output hash mismatch: expected {expected_hash}, got {output_hash}")
|
raise ValueError(f"Output hash mismatch: expected {expected_hash}, got {output_hash}")
|
||||||
|
|
||||||
|
# Get effects repo commit
|
||||||
|
effects_commit = get_effects_commit()
|
||||||
|
|
||||||
# Build provenance
|
# Build provenance
|
||||||
provenance = {
|
provenance = {
|
||||||
"task_id": self.request.id,
|
"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}
|
{"content_hash": input_hash}
|
||||||
],
|
],
|
||||||
"effects": [
|
"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": {
|
"infrastructure": {
|
||||||
"software": {"name": "infra:artdag", "content_hash": REGISTRY["infra:artdag"]["hash"]},
|
"software": {"name": "infra:artdag", "content_hash": REGISTRY["infra:artdag"]["hash"]},
|
||||||
|
|||||||
Reference in New Issue
Block a user