Skip to content

fix(registry): catalog import derives vendor-doubled api_name; warn on unmatched credential scope - #1067

Open
ren-jentic wants to merge 4 commits into
mainfrom
fix/1020-catalog-import-doubled-api-name
Open

ren-jentic wants to merge 4 commits into
mainfrom
fix/1020-catalog-import-doubled-api-name

Conversation

@ren-jentic

@ren-jentic ren-jentic commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

A plain jentic catalog import <domain/sub> seeded the ingest api_name with the full catalog id, which slugification collapsed into a vendor-doubled workspace identity (posthog-com/posthog-com-posthog-api) that no other surface agrees with — and since credential creation accepts an unvalidated API reference, a credential bound to the expected clean name silently never matched and every execute 403'd with no_toolkit_binding. This fixes the identity derivation at import time and surfaces the credential-scope mismatch at create time instead of at execute time.

Plan: jentic-one-plans/issues/issue-1020-catalog-import-vendor-doubled-api-name.md.

Changes

  • registry: _to_import_source seeds api_name from the catalog id's sub segment (posthog.com/posthog-apiposthog-api), so the workspace identity matches the catalog path, repo layout, and broker upstream registration. Bare-domain entries (coincap.io) keep their established identity.
  • registry: collision guard in ResolveApiStage — two distinct catalog entries that collapse to the same registry identity (stripe.com/x vs api.stripe.com/x, possible now that names are clean) refuse the import with a readable catalog identity conflict error instead of silently stacking a foreign spec as a new revision. Same-id re-imports and NULL backfills (Persist the catalog identity (api_id) at import and expose it on credential/binding/API DTOs #910 semantics) are unchanged.
  • control: CredentialService.create now checks (best-effort) whether the canonical scope covers any imported registry API, via a new raw-SQL RegistryApiLookupRepository following the broker's cross-tier pattern (no cross-imports). On a miss it warns, never rejects: warnings field on the create response, a new warning-severity credential.unmatched_api event, and a structured log — with a nearest-identity hint naming the vendor's imported APIs. Skipped on standalone control deployments (no registry DB) and on registry read errors.
  • OpenAPI spec regenerated (make openapi).
  • Out of scope (per the plan, follow-up PR): migrating existing doubled identities in installed deployments — needs a coordinated registry+control re-slug migration; the import fix stops the bleeding for all new imports first.

Risk & rollback

  • New response field (warnings, nullable) on POST /credentials — additive, no client breakage.
  • New event type credential.unmatched_api — UI event rail falls back to the generic credential icon for unknown credential.* types.
  • Identity change applies only to new catalog imports of domain/sub entries; existing workspace rows are untouched (re-importing one after upgrade creates the clean identity alongside the old doubled row — resolved properly by the follow-up migration).
  • Revert the squash commit to roll back.

Test plan

  • make lint (ruff + mypy) clean; tests/arch 274 passed
  • make test-fast, make test-integration (Postgres), make test-integration-sqlite — all green
  • New tests: tests/unit/registry/ingest/test_extract_api_catalog_conflict.py (collision guard, real SQLite DB), tests/integration/control/test_credential_unmatched_api_warning.py (cross-DB: registry seed → control create → admin event), updated tests/web/registry/test_catalog.py to pin the sub-segment derivation
  • Pre-existing/environmental failures unrelated to this change (identical on untouched main checkout): test_spec_is_valid[broker] (needs network for a remote $ref), make score (private npm package 404)

Review follow-ups (8-lens deep review)

Hardening applied in this PR after review:

  • Time-bounded advisory probe: the registry lookup at credential create is wrapped in asyncio.timeout(2s) and gated on has_db("registry") — a hung or never-connected registry DB can neither stall POST /credentials nor log-spam; timeouts land in the existing best-effort fallback.
  • Collision guard hardening: the refusal is now a typed CatalogIdentityConflictError naming both catalog ids and the next step; the guard read takes FOR UPDATE (closes the READ COMMITTED backfill race where two colliding imports could both pass); uq_apis_vendor_name_version maps to a readable job error for the insert race.
  • Event hygiene: credential.unmatched_api now carries a short bounded summary (fits the 512-char column and one-line UI rows), the remedy + sibling-identity hint in detail, and credential_id in data; warning text names both remedies (import the API, or delete + re-create the immutable-scope credential); emit-failure log no longer misattributes an event type.
  • Test gaps closed: end-to-end catalog import through the real _to_import_sourceImportHandler seam asserting the clean identity lands; conflict surfacing as a readable job failure; POST /credentials web tests for the warnings field (present + null); version-only wildcard, standalone-control skip, and registry-error probe branches; shared SQLite apis fixture promoted to tests/unit/registry/conftest.py.

Ticketed follow-ups: #1076 (UI surfaces warnings), #1077 (unmatched-api event race with the import job), #1078 (A4b transition edge over doubled rows), #1079 (Phase 3 migration of existing doubled identities).

Closes #1020

Made with Cursor

ren-jentic and others added 4 commits August 17, 2026 16:22
…o imported API (#1020)

Co-authored-by: Cursor <cursoragent@cursor.com>
Review follow-ups on the credential-create advisory and the catalog
identity guard:

- time-bound the best-effort registry probe (asyncio.timeout 2s) so a
  hung registry DB cannot stall POST /credentials; gate on has_db so a
  never-connected context skips instead of logging on every create
- type the guard's refusal as CatalogIdentityConflictError, take the
  guard read FOR UPDATE (closes the READ COMMITTED backfill race), and
  map uq_apis_vendor_name_version to a readable job error
- keep the unmatched-api event summary short and bounded; move the
  remedy + sibling-identity hint to detail and carry credential_id in
  data; name both remedies (import vs delete-and-re-create) in the
  warning; fix the emit-failure log misattribution
- tests: e2e catalog import lands the clean identity through the real
  _to_import_source -> ImportHandler seam; the conflict surfaces as a
  readable job failure; POST /credentials web tests for the warnings
  field; version-only wildcard, standalone-skip and registry-error
  probe branches; promote the shared SQLite apis fixture to conftest

Co-authored-by: Cursor <cursoragent@cursor.com>
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.

catalog import derives vendor-doubled api_name (domain/sub) → credential binding silently never matches

1 participant