15 Commits
Author SHA1 Message Date
Hanzo AI e16131b0b6 studio: manifest indexes .glb — 3D meshes survive the sweep (kind=model3d)
The build-manifest sidecar scanned only image/video/marketing extensions, so a
.glb was dropped on every rebuild — the ingested mesh vanished from the library
within a sweep cycle (same class as the earlier video/ingest drops). Add
MODEL3D_EXTS to the scan and the kind default.
2026-07-18 10:02:49 -07:00
Hanzo AI 100ca8d8a2 studio: structural fix — duplicate renders impossible by construction
The retry-cap + dispatch-dedup patches stop the two known triggers; this makes the
whole class impossible at every layer, independent of any upstream bug:

COMPUTE — don't render a duplicate:
  * retry cap (maximumAttempts=1): a failed render never re-runs itself.
  * dispatch dedup: identical (org, prefix, instruction, refs) submits within 60s
    collapse to the in-flight render.

QUEUE — nothing lingers to replay:
  * scheduleToStartTimeout=1800s: a job no worker picks up in 30 min EXPIRES, so a
    worker outage can't leave a backlog that silently re-bills when a worker returns.

STORAGE — the gallery is CONTENT-ADDRESSED (the last line of defense):
  * library_manifest (the one indexer that builds every library.json, sweeping each
    org continuously) now enforces AT MOST ONE active asset per content hash. Any
    byte-identical duplicate is auto-soft-deleted, keeper = a curated asset if one
    exists else the earliest. Idempotent, recoverable, and O(new-files) per sweep
    (sha reused when mtime is stable). So even 873 identical re-renders can NEVER
    accumulate in the gallery — the invariant heals it within one sweep.

Verified: 3 identical + 1 distinct -> 2 active (one-per-hash), distinct untouched,
stable across sweeps.
2026-07-18 09:56:13 -07:00
studio-implandHanzo AI 3565974382 studio: video lane — generate/card/ingest/judge over the shared library and flywheel 2026-07-18 02:02:30 -07:00
Hanzo AI 193aee3aac studio: fix the library.json curation-wipe race + make /ready check worker liveness (Red HIGH x2)
LIBRARY.JSON CORRUPTION (Red V3, the curation-wipe mechanism): the build-manifest
sidecar wrote a FIXED '.library.json.tmp', shared by every pod's sidecar — two
overlapping during a roll interleaved into one inode → torn JSON → load_prior()/
reader hit JSONDecodeError → {} → every asset reset to status=draft, which mirror
then pushed to S3 and restore rehydrated. Fixed both writers: mkstemp gives each a
private tmp (kills the tear), and a shared flock (.library.lock, taken by the
sidecar across build+write and by the app's _save_library across its write)
serializes the two rebuild-then-replace paths so neither clobbers the other.

/READY LIVENESS (Red V1): the readiness probe checked ,
set once at boot and never cleared — constant-true, saying nothing about the render
path. A dead worker thread kept the pod in-rotation accepting jobs that never ran,
and a roll would promote a wedged pod. /ready now gates on the prompt_worker daemon
thread actually being alive.

Tests: unique-tmp, no fixed-tmp residue, concurrent-writers-never-tear, lock file.
226 pass.
2026-07-18 01:27:54 -07:00
fac9880aaf library: hidden segments are never assets (#24)
The everything-indexes sweep pulled .thumbs/ cache files in as rows; fixing
one sent a thumb path into the dispatcher (unknown asset). One rule, both
writers: any path segment starting with a dot is excluded — forks and cache
trees alike.

Co-authored-by: Hanzo AI <ai@hanzo.ai>
2026-07-17 13:59:52 -07:00
bb3711eca1 manifest: taxonomy enriches, never gates (#23)
The sweeper rebuilt library.json and DROPPED every image classify() could
not parse — erasing ingest-indexed renders on each cycle (rows appeared,
then vanished minutes later). Unclassifiable images now keep a minimal row,
prior fields preserved.

Co-authored-by: Hanzo AI <ai@hanzo.ai>
2026-07-17 13:17:47 -07:00
b5bbbfd724 library: dotfiles are never assets (#18)
The indexer skips them; _safe_asset refuses them (one gate for file serving,
fix sources, and every other path lookup). AppleDouble forks carry image
extensions, serve resource-fork bytes, and rendered 0x0 across the library.

Co-authored-by: Hanzo AI <ai@hanzo.ai>
2026-07-17 12:14:02 -07:00
hanzo-dev 0a3146e00f docs: de-tenant the manifest + home docstrings
Claude-Session: https://claude.ai/code/session_01Gq8suw7uuodAMPDRpo6iAB
2026-07-14 17:06:03 -07:00
hanzo-dev 8e55ecac9a refactor: karma_manifest.py -> library_manifest.py — org-generic name for the org-generic tool (unified multi-tenant SaaS; no tenant names in code)
Claude-Session: https://claude.ai/code/session_01Gq8suw7uuodAMPDRpo6iAB
2026-07-14 17:05:44 -07:00
Hanzo AI ef128fadcc chore: drop watchdog script — hanzo gpu connect --studio-dir supervises the render backend (one way) 2026-07-12 22:25:03 -07:00
Hanzo AI 4200c52000 fix(packs): comfyui_segment_anything under Transformers 5; installer applies scripts/patches/<pack>-*.patch
BertModelWarper now delegates to the real BertModel.forward instead of
reimplementing internals whose private signatures drift between releases;
checkpoint weights remap bert.* -> bert.bert_model.* at load. The installer
force-checkouts each pin (discarding prior patches) then applies any
scripts/patches/<pack>-*.patch, so re-runs stay idempotent and the image
build gets the same fix the GB10 venv had by hand.
2026-07-12 22:08:44 -07:00
Hanzo AI 1d67adb0fb feat(scripts): karma library manifest generator + studio watchdog (from the GB10 box)
karma_manifest.py regenerates the ONE canonical library.json index of the
karma org's rendered assets (storefront + socials queue both read it).
studio_watchdog.sh keeps the local render backend on :8188 alive unattended;
paths derive from the script location (STUDIO/STUDIO_PY overridable).
2026-07-12 22:08:44 -07:00
hanzo-dev a9165125b5 studio packs: vendor top ComfyUI node packs as first-class hanzo packages
Add a comfy->studio import shim so upstream ComfyUI custom nodes run
unmodified against this fork's renamed core, then vendor the popular packs
(Impact-Pack/Subpack, IPAdapter_plus, controlnet_aux, Ultimate SD Upscale,
Frame-Interpolation, VideoHelperSuite, rgthree, Custom-Scripts, KJNodes,
cg-use-everywhere, SAM2, segment_anything) pinned to immutable commits and
reproducibly rebuilt in the image.

- studio_compat: one meta-path finder aliasing comfy*->studio* and mirroring
  Comfy*->Studio* API classes (the deferred phase-4 of scripts/rename_modules.py,
  done with the 3.12 find_spec API); activated by one import at top of main.py.
- nodes.py: honor the upstream `comfy_entrypoint` V3 extension name too.
- custom_nodes/hanzo-packs.txt: pinned pack manifest (SHAs + SPDX licenses).
- custom_nodes-requirements.txt + scripts/install_custom_nodes.sh: pack deps
  with the cu130 torch / NumPy 2.x / Transformers 5.x stack locked (derived
  from the live env, never downgraded); ultralytics installed --no-deps.
- Dockerfile/.dockerignore/.gitignore: image vendors the packs at build.

Live GB10 worker verified: node types 644 -> 1290 (+646), 0 import failures.
was-node-suite quarantined (hard numba dep incompatible with NumPy 2.5).
2026-07-04 13:19:09 -07:00
hanzo-dev b521933ca3 fix(server): graceful SIGTERM/SIGINT shutdown + crash-durable queue
Root cause of the "Event loop stopped before Future completed" crash that
wiped the in-memory queue/history (seen 3x today): the fork's _graceful_shutdown
handler (added in a45a441) called event_loop.stop(). The server coroutine passed
to run_until_complete() never completes on its own, so stopping the loop makes
run_until_complete() raise RuntimeError; the surrounding try only caught
KeyboardInterrupt, so the process died non-zero with a traceback and discarded
the queue. The dirty exit also failed to release :8188 promptly, and a duplicate
transient/user `hanzo-studio.service` racing the canonical studio.service turned
that into an EADDRINUSE restart storm.

main.py:
- handler is now idempotent, flags the queue closed, snapshots it, then stops
  the loop; the outer try treats RuntimeError('Event loop stopped before Future
  completed.') as the expected graceful-exit path (logs "Server stopped by
  shutdown signal", exits 0). Verified live in journald.

execution.py:
- opt-in crash-durable PromptQueue behind STUDIO_PERSIST_QUEUE=1. Pending +
  in-flight prompts are snapshotted atomically to user/queue_snapshot.json on
  every change and re-queued on boot. Best-effort: all I/O is guarded so it
  never affects the render path; inert when the flag is unset.

ops/systemd/studio.service:
- enable STUDIO_PERSIST_QUEUE=1 on the single canonical unit.

scripts/studio-service-swap.sh:
- batch-safe reconcile+apply: waits for an empty queue, removes any stray
  duplicate unit, reinstalls the canonical unit, restarts, health-checks.

docs/ops-crash-and-service.md:
- topology (one canonical unit), how to read crash history, the fix, persistence.

tests-unit/execution_test/prompt_queue_persist_test.py:
- round-trip, inert-when-disabled, and corrupt-row-skipping tests.
2026-07-03 14:42:03 -07:00
Hanzo Dev 5cf6ce2508 Rename all internal modules: comfy* → studio*
Complete module rename across 552 files:
- comfy/ → studio/
- comfy_extras/ → studio_extras/
- comfy_api/ → studio_api/
- comfy_api_nodes/ → studio_api_nodes/
- comfy_config/ → studio_config/
- comfy_execution/ → studio_execution/
- comfy/comfy_types/ → studio/node_types/
- comfyui_version.py → studio_version.py
- All test directories renamed
- All import statements updated
- All internal identifiers renamed (ComfyNodeABC → StudioNodeABC, etc.)
- All wire protocol types renamed (COMFY_* → STUDIO_*)
- No backward compatibility shims — clean break
- External pip packages preserved (comfyui-frontend-package, comfy-kitchen, etc.)
- 518 Python files syntax-validated, 0 errors
2026-02-23 12:49:38 -08:00