Skip to content

fix(bridge): make isolated re-scan idempotent by seeding on function id - #390

Merged
yeongseon merged 1 commit into
mainfrom
fix/388-isolated-rescan-idempotent
Aug 10, 2026
Merged

fix(bridge): make isolated re-scan idempotent by seeding on function id#390
yeongseon merged 1 commit into
mainfrom
fix/388-isolated-rescan-idempotent

Conversation

@yeongseon

Copy link
Copy Markdown
Owner

Summary

  • Fixes a regression in the opt-in app-scoped registry isolation path (refactor: opt-in app-scoped registry isolation (#381) #388) where scanning the same app twice produced phantom endpoints (route-less /api/handler_one) and duplicate operationIds.
  • _seed_canonical_entry now guards on function identity (reg.find_by_function_id(function_id)) instead of key presence, so a canonical entry that was exploded + had its original key deleted during reconcile is not re-seeded on a subsequent scan.

Root cause

The previous guard used reg.get(key) is None. Reconcile explodes a method=None canonical into method::path keys and deletes the original key (#358). A second scan re-seeded the deleted key -> route=None phantom canonical -> spec.py renders it with function name + GET fallback. The global path was already idempotent; only isolation regressed.

Changes

  • src/azure_functions_openapi/bridge.py: identity-guarded seeding in _seed_canonical_entry (sequential lock acquisition: snapshot under global lock, identity-guarded set under registry lock). Docstring updated.
  • tests/test_spec_warnings.py: two regression tests under TestIsolatedRegistry (test_isolated_rescan_is_idempotent, test_isolated_shared_handler_scan_no_phantom) + _binding_only_app helper. Verified both fail without the fix.

Verification

  • make check-all green; coverage 96.23% (688 passed, 5 skipped).

Closes #389

Isolated-registry seeding judged idempotency by registry-key presence, but
reconciliation explodes a seeded method=None canonical into a method::path
entry and deletes the original key (#358). A second scan therefore re-seeded
the vanished key, resurrecting a stale route=None canonical that spec.py then
documented as a phantom GET /api/<function-name> endpoint -- and, when an app
and its Blueprint share one handler, tripped a duplicate operationId in a
single generation. Seeding now short-circuits when any entry for the handler's
_function_id already exists, so re-scans (and app+Blueprint combinations) stay
idempotent. The global registry path was already idempotent and is unchanged.

Adds regression tests for a same-app re-scan and a shared-handler app+Blueprint
scan into one isolated registry.
@yeongseon
yeongseon requested a lite review from Copilot August 10, 2026 12:06
@yeongseon
yeongseon merged commit d313539 into main Aug 10, 2026
17 checks passed
@yeongseon
yeongseon deleted the fix/388-isolated-rescan-idempotent branch August 10, 2026 12:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Fixes a regression in the app-scoped (isolated) registry scan path where re-scanning the same app/handler could re-seed a deleted canonical key and produce phantom endpoints + duplicate operationIds. The change makes isolated seeding idempotent by guarding on handler identity (_function_id) rather than key presence, and adds regression tests covering double-scan and shared-handler scenarios.

Changes:

  • Update _seed_canonical_entry to skip seeding when the isolated registry already contains any entry for the same _function_id.
  • Add isolated-mode regression tests to ensure re-scan idempotency and prevent phantom endpoints when the same handler is scanned twice (e.g., blueprint + app).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/azure_functions_openapi/bridge.py Adjusts isolated seeding logic to be identity-based and avoid re-seeding after reconcile rewrites keys.
tests/test_spec_warnings.py Adds regression coverage for isolated re-scan idempotency and shared-handler scans, plus a helper for binding-only canonical shapes.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +493 to +496
if reg.find_by_function_id(function_id) is not None:
return
for key, entry in seeds.items():
if reg.get(key) is None:
reg.set(key, entry)
reg.set(key, entry)
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.

fix(bridge): isolated re-scan produces phantom endpoints (non-idempotent seeding)

3 participants