Add config/recipe support for DAG-based jobs

- Add PyYAML dependency for parsing config files
- Add Pydantic models: VariableInput, FixedInput, ConfigStatus, ConfigRunRequest
- Add Redis storage functions for configs
- Add config YAML parsing with variable and fixed input detection
- Add config API endpoints: upload, list, get, delete, run
- Add config UI: Configs tab, list page, detail page with run form
- Add HTMX endpoints for config operations
- Add pinning on publish: configs and their fixed inputs are pinned
  when runs from configs are published to L2
- Clean up debug logging in cache_manager

Config YAML format supports:
- Fixed inputs: resolve asset hashes from registry
- Variable inputs: marked with `input: true`, filled at run time
- DAG definition with nodes and edges
- Registry of assets and effects

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gilesb
2026-01-08 03:17:50 +00:00
parent f23a721816
commit 4a99866602
3 changed files with 675 additions and 46 deletions

View File

@@ -277,31 +277,25 @@ class L1CacheManager:
def get_by_content_hash(self, content_hash: str) -> Optional[Path]:
"""Get cached file path by content_hash."""
logger.info(f"get_by_content_hash({content_hash[:16]}...) - cache_dir={self.cache_dir}, nodes_dir={self.cache.cache_dir}")
# Check index first (new cache structure)
node_id = self._content_index.get(content_hash)
if node_id:
logger.info(f" Found in content_index: node_id={node_id[:16]}...")
path = self.cache.get(node_id)
if path and path.exists():
logger.info(f" Found via index: {path}")
return path
logger.info(f" Index entry but path not found: {path}")
# For uploads, node_id == content_hash, so try direct lookup
# This works even if cache index hasn't been reloaded
logger.info(f" Trying direct lookup with content_hash as node_id...")
path = self.cache.get(content_hash)
logger.info(f" cache.get({content_hash[:16]}...) returned: {path}")
if path and path.exists():
logger.info(f" Found via direct lookup: {path}")
self._content_index[content_hash] = content_hash
self._save_content_index()
return path
# Scan cache entries (fallback for new structure)
logger.info(f" Trying find_by_content_hash...")
entry = self.cache.find_by_content_hash(content_hash)
if entry and entry.output_path.exists():
logger.info(f" Found via scan: {entry.output_path}")
@@ -311,12 +305,9 @@ class L1CacheManager:
# Check legacy location (files stored directly as CACHE_DIR/{content_hash})
legacy_path = self.cache_dir / content_hash
logger.info(f" Checking legacy path: {legacy_path}, exists={legacy_path.exists()}")
if legacy_path.exists() and legacy_path.is_file():
logger.info(f" Found at legacy location: {legacy_path}")
return legacy_path
logger.info(f" NOT FOUND anywhere")
return None
def has_content(self, content_hash: str) -> bool: