Introduces a forward-only, additive, idempotent migration framework on top of the existing legacy implicit-repair path (which remains the source of truth for schema versions <= 16). Versioned migrations live in mjr_am_backend/adapters/db/migrations/ and are tracked in a new schema_migrations bookkeeping table. The runner is invoked on every startup right after migrate_schema(); pending migrations are applied in version order, already-applied ones short-circuit.
First migration v17 normalize_tags creates a parallel tags + asset_tags relational layer (case-insensitive UNIQUE on tag name, cascade-delete on asset side, covering index on tag_id). Backfill reads asset_metadata.tags JSON safely (malformed rows skipped) and uses INSERT OR IGNORE everywhere for full idempotency. The legacy JSON column is NOT touched; existing v16 readers keep working.
Adds a Repository layer under mjr_am_backend/data/repositories/ to isolate raw SQL from services. First repository TagsRepository exposes ensure / get_by_name / list_all / attach / detach / list_for_asset / list_assets_with_tag / count on the new normalized tables, all returning Result.
Tests: 6 migration-runner tests (version validation, idempotency, bookkeeping, failure propagation, registry application) and 7 TagsRepository tests (CRUD, case-insensitivity, cascade-delete) - 13/13 passing. Existing test suite remains green (regressions limited to 2 pre-existing health_routes failures unrelated to this change).
Phase 3.2 closes the DB hardening item of the v3 improvement plan.
Adds a declarative blueprint.{yaml,json} manifest layer on top of the existing dynamic-module plugin loader. Each plugin can now ship as a self-contained package with a manifest describing name, version, author, type, entrypoint class, and capabilities. The legacy flat .py plugin format remains fully supported.
Manifest schema v1 with fields: schema_version, name (slug), version (semver), author, type (metadata_extractor), entrypoint.{module,class}, optional capabilities/tags/license/homepage/description/min_majoor_version. Path-traversal and module-name injection are rejected at parse time. YAML manifests require PyYAML (already shipped by ComfyUI core); JSON manifests need no extra deps.
Loader: PluginLoader.discover_plugins now treats subdirectories with a blueprint manifest as blueprint packages (taking precedence over plain Python-package scanning). New PluginLoader.blueprints property and get_stats[blueprints] field expose loaded manifests for listing endpoints.
Migrated bundled examples to blueprint layout: plugins/examples/wanvideo/, plugins/examples/custom_node_template/, plugins/examples/rgthree/ (YAML variant).
Tests: 11 new tests in tests/features/test_blueprint_plugins.py covering manifest parsing edge cases (bad name, bad semver, unknown type, path traversal, schema mismatch), YAML support, discovery skipping invalid manifests, and loader integration including a missing-entrypoint warning path.
Read-only surface mirroring ComfyUI core OpenAPI assets spec under a /api/v2/ prefix to avoid collision with the existing /api/assets/* endpoints served by core.
Endpoints:
- GET /api/v2/assets (list with name_contains, include/exclude_tags, sort, limit/offset)
- GET /api/v2/assets/{id} (numeric ID, blake3:hex hash, or filename fallback)
- GET /api/v2/assets/{id}/content (FileResponse, path-allowlist enforced)
- GET /api/v2/assets/{id}/tags
- HEAD /api/v2/assets/hash/{hash} (existence check)
11 unit tests in tests/features/test_api_v2_assets.py covering payload shape, pagination, hash check (match/miss/malformed), 404 paths, and disallowed content paths.
- Add AI.VectorCaptionOnIndex UI setting with rollback on API error
- Backend: persist/read vector_caption_on_index via AppSettings
- Expose set_vector_caption_on_index via health route and clientOps API
- Wire is_vector_caption_on_index_enabled() into config + env vars
- Update vector_indexer to honour the caption-on-index flag
- Add tests: settings backend, health routes, vector indexer video alignment
- Extend grid_scope_switch_cache test coverage
WorkflowSidebar.js no longer imports comfyApiBridge.js directly (logic
moved to WorkflowNodesTab). Sync the ADR 0007 ratchet set with the live
count so test_count_matches_known_violations_set passes.
Live count: 6 — KNOWN_FEATURE_VIOLATIONS now also has 6 entries.
- Replace direct comfyApiBridge import in WorkflowNodesTab with getRawHostApp (ADR 0007).
- Reset security prefs snapshot in _reset_security_state_for_tests so tests that toggle MAJOOR_REQUIRE_AUTH are not influenced by an earlier ensure_security_bootstrap publishing require_auth=False.
- Call _reset_security_state_for_tests from _clear_write_auth_env helper for proper isolation.
- Rebuild frontend bundle.
- Adjusting the infinite scroll loading point to 4 card rows from the bottom
- Improving grid state restoration after reloads
- Enhancing the early fetch mechanism for faster initial loads
- Optimizing execution runtime to handle large numbers of assets efficiently
- Updating viewer and run button behavior for better integration with ComfyUI
- Fix various bugs and edge cases
- Add special handling for primitive string nodes in text fallback
- Set text and value from non-empty primitive string nodes
- Update tests to cover new behavior
- FloatingViewer.impl.js: add missing _resetMfvZoom() method (was called
from floatingViewerLoader/Mode but never defined on the class)
- viewerShell.js: return genInfoTitle + genInfoTitleLeft from createViewerStageShell
- ViewerRuntime.js: destructure genInfoTitle + genInfoTitleLeft (were undefined
=> gen info body was never populated)
- floatingViewerPopout.js: inject document.documentElement.style.cssText as
:root{...} in popup head so ComfyUI CSS vars are available; set body
background to #0d0d0d; remove broken history.replaceState (Chrome blocks
it from about:blank)
- __init__.py: remove unused FailureSeverity/StageStatus imports