Skip to content

FEAT: Restore saved registry entries and stay not ready if any fail - #3909

Open
varunj-msft wants to merge 64 commits into
microsoft:mainfrom
varunj-msft:varunj-msft/13178-Restore-Saved-Entries
Open

varunj-msft wants to merge 64 commits into
microsoft:mainfrom
varunj-msft:varunj-msft/13178-Restore-Saved-Entries

Conversation

@varunj-msft

@varunj-msft varunj-msft commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Description

This is one of the changes for saving Registry entries across restarts. It's the last of four stacked PRs and builds on #3908. Until the earlier PRs merge, the diff here also shows their changes.

Problem. Saved entries need to come back at startup and after a live apply. A broken one must stop runs instead of vanishing; otherwise a scan that expects a saved converter or adversarial_chat would run with something else, or fail later.

Change.

  • New setting registry_recipes_source: a local folder or Azure Blob location, default ~/.pyrit/registry_recipes. It's validated like scenario_presets_source and documented in pyrit_conf.md and .pyrit_conf_example.
  • RuntimeLifecycle restores saved entries in two places. At startup, after the initializers run and the configuration version is read. On apply, after the new version is recorded; apply already resets the registries, so restore starts clean. The recipes are read in a worker thread, and the entries are built on the runtime loop, as the target API builds them, since a constructor such as HuggingFaceChatTarget's starts a task on that loop. As with the target API, a slow constructor holds the loop while it runs.
  • If anything fails, the runtime stays failed with outcome restore-failed and doesn't publish. The middleware already returns 503 for runtime routes, and /api/health shows ready: false. GET /api/config/runtime lists restore_failures and restore_ignored, and the failures are logged. For now the fix is to correct or delete the file and restart.
  • The backend unit tests, the analytics benchmark fixture and the offline browser fixture point the recipes folder at a temp folder, so they don't read the developer's own saved entries; the backend README says so for the browser fixture. The seeded Playwright backend and the end-to-end test backend still use the default folder. Nothing writes saved entries yet, so isolating them comes with the PRs that save entries.

What stays the same. With no saved entries, startup and apply behave as before. A file whose name isn't a valid entry name is listed as ignored and doesn't block readiness.

Next. Fixing or deleting a broken entry without a restart, and showing the failure list to every signed-in user rather than only in the admin runtime status, come with the recovery routes in the next PR.

Tests and Documentation

  • tests/unit/backend/test_runtime_lifecycle.py: startup and apply restore an entry that uses a target the startup scripts register, the recipes are read in a worker thread and restored entries are built on the runtime loop, startup and apply with a broken entry stay not ready and list it in the runtime status, ignored files don't block readiness, fixing the file and restarting reaches ready, cancellation still propagates, and a reference name or file name that isn't valid text still returns 200. tests/unit/setup/test_configuration_loader.py covers the new setting; tests/unit/backend/conftest.py, the analytics benchmark fixture and the offline browser fixture (frontend/e2e/fixtures/manual_send_backend.py) point the recipes folder at a temp folder, so they never read a developer's saved entries.
  • pytest tests/unit/backend tests/unit/setup tests/unit/registry tests/unit/models -n 4: 8,053 passed, 9 skipped, 4 xfailed
  • pre-commit run --from-ref origin/main --to-ref HEAD: passed
  • Ran the backend locally against a recipes folder with a target whose temperature can't be read, a pool that references a missing target, a very long value, and a file name that isn't valid UTF-8: /api/health showed ready: false, GET /api/config/runtime returned 200 with all three failures and the escaped file name, and neither the status nor the log held the test secret. After deleting the three broken files and restarting, it reached ready, with the oddly named file still listed as ignored.
  • Also ran it with a saved HuggingFaceChatTarget (offline, so only its background model load fails): it reached ready at startup and after a live apply. With the restore in a worker thread instead, startup failed with no running event loop.
  • Docs: doc/getting_started/pyrit_conf.md and .pyrit_conf_example describe registry_recipes_source. JupyText: not run (no notebooks changed).

…API input

External construction now rejects a parameter whose type includes Path unless the
calling service passes the name in allowed_path_params after making the value safe.
The converter service allows its upload parameters once they are stored, and the
target service allows the server-set upload folder and the GitHubCopilotTarget
working directory. The scorer-only check is removed, and the "Valid parameters"
list leaves out path parameters callers cannot set.
The target catalog now applies the path rule to required parameters too, so a
target that needs a disallowed path is left out instead of listed without the
field, matching the scorer catalog. allowed_path_params is typed frozenset[str]
so a bare string cannot turn the allow-list into a substring match.
The target catalog still lists a target whose required path parameter is its
server-set upload folder when the server has one, and leaves it out otherwise.
# Conflicts:
#	tests/unit/backend/test_converter_service.py
Copilot AI balanced review requested due to automatic review settings November 4, 2026 06:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Offloading the entire restore runs loop-dependent constructors without an active event loop, causing valid saved HuggingFace model recipes to fail and block startup or apply.

1 open issue
What changed in this PR

Adds restoration of saved registry entries to PyRIT’s backend startup and live configuration apply, building on the dependency-aware restoration in #3908. Failed restores keep the runtime unavailable rather than silently omitting required components.

Changes:

  • Adds recipe models, local/Azure Blob storage, and dependency-ordered restoration.
  • Integrates restoration with runtime readiness and exposes failures and ignored files.
  • Documents storage configuration and adds tests with isolated recipe folders.
File Description
tests/​unit/​setup/​test_configuration_loader.py Tests recipe-source configuration and validation.
tests/​unit/​registry/​test_registry_recipe_storage.py Tests recipe parsing, storage, and report serialization.
tests/​unit/​registry/​test_registry_recipe_restore.py Tests dependency ordering, validation, and restore failures.
tests/​unit/​registry/​test_file_document_storage.py Tests local and Blob listing diagnostics and file races.
tests/​unit/​backend/​test_runtime_lifecycle.py Tests restoration, readiness, recovery, and cancellation.
tests/​unit/​backend/​test_analytics_benchmark.py Verifies benchmark recipe-folder isolation.
tests/​unit/​backend/​conftest.py Isolates saved entries for backend unit tests.
test_support/​analytics_benchmark.py Configures a benchmark-specific recipe folder.
pyrit/​setup/​configuration_loader.py Adds and validates registry_recipes_source.
pyrit/​registry/​registry_recipe_storage.py Reads and validates versioned registry recipes.
pyrit/​registry/​registry_recipe_restore.py Rebuilds entries in dependency order and reports failures.
pyrit/​registry/​file_document_storage.py Adds listings that report unreadable and ignored documents.
pyrit/​models/​request_limits.py Adds a shared text-length limit.
pyrit/​models/​catalog/​registry_recipe.py Defines recipe and restore-report models.
pyrit/​models/​catalog/​__init__.py Lazily exports the new catalog models.
pyrit/​backend/​services/​runtime_lifecycle.py Restores entries before publishing readiness.
pyrit/​backend/​README.md Documents offline browser-fixture isolation.
frontend/​e2e/​fixtures/​manual_send_backend.py Uses a temporary recipe folder.
doc/​getting_started/​pyrit_conf.md Documents recipe storage and failure recovery.
.pyrit_conf_example Adds an example recipe-storage setting.

Review effort: Balanced

bool: Whether every saved entry was restored.
"""
restorer = RegistryRecipeRestorer(source=config.registry_recipes_source)
report = await asyncio.to_thread(restorer.restore)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants