Picker Content Boundary: Query Dimensions + Write Schema¶
Portal and content service boundary¶
From: Portal Plugin Factory team To: Content Management team Status: Draft — supersedes previous "operation contract" proposal Context: Content extraction and picker contracts
1. Background¶
The self-service plugin contains seven scaffolder field pickers. As part of the planned content extraction, these are split by data ownership:
- Pickers that query Ansible content (collections, EE images) — the content boundary defined in this document. The Content Management team owns the data; we own the picker widget.
- Pickers that query AAP resources (organizations, inventories, projects) — continue to use existing AAP API routes. Not part of this contract.
- Pickers that are pure user-input (tags, pip package names, file names, MCP servers, SCM choices) — free text or template-enum. No content boundary. Not part of this contract.
What we are agreeing on here:
- The query dimensions each picker needs (what fields are queryable on each content type)
- The write schema (what the template form persists after a picker selection)
- The authz requirement per query
- The
sourceIdstability invariant (non-negotiable — stored values must survive upgrades)
What we are NOT prescribing: how the query is implemented. Whether sources and versions arrive as facets on a search query, relations, or a separate index is the Content Management team's decision. The picker is a widget; we do not own the query engine.
The two pickers in scope¶
| Picker component | Content it queries |
|---|---|
CollectionsPickerExtension |
Ansible collections from PAH / Galaxy / OCI |
BaseImagePickerExtension |
Built EE images available as base images |
2. Current Behaviour (What We Ship Today)¶
Understanding the current implementation makes the required data surface explicit.
2.1 CollectionsPicker¶
The picker today calls scaffolderApi.autocomplete with three resource strings in a
cascade: name → source → version. The user flow is:
- Type to search collection names → select one
- Select a source (PAH instance, Galaxy, OCI registry) where that collection is available
- Optionally select a version at that source (or leave blank for "latest")
The value stored in the template form is:
interface CollectionItem {
name: string; // e.g. "community.general"
source: string; // source ID — stored in the EE definition file
version?: string; // version string, or null for "latest"
}
2.2 BaseImagePicker¶
Currently static — available base images are hardcoded as a JSON schema enum in
the template YAML. No API call is made at runtime.
The planned integration requires this to become dynamic — fetching available base images from the content catalog so that new AAP releases appear automatically without template edits.
The value stored in the template form is:
interface EEBaseImageRef {
imageRef: string; // fully qualified: "registry/image:tag"
digest?: string; // sha256:... for reproducible pinning (preferred for production)
}
3. Content Boundary: Query Dimensions + Write Schema¶
3.1 Collection type¶
What the picker needs to query:
| Dimension | Description | Notes |
|---|---|---|
name |
Full-text / prefix search on collection name (e.g. "community.general") | Powers the first autocomplete |
sources |
For a selected collection: what sources carry it (PAH, Galaxy, OCI, etc.) | A relation or facet — not a separate RPC. Sources are IDs + human labels |
versions |
For a selected collection + source: available versions | May be live-fetched or indexed — freshness is the content service's concern, not ours |
Write schema (what we store and what must stay stable):
interface CollectionItem {
name: string; // fully qualified collection name — stable
source: string; // opaque source ID — MUST survive PAH upgrades (see §4)
version?: string; // version string, or null/omitted for "latest"
}
Authorization: ansible.collections.view (already declared in backstage-rhaap-common)
3.2 Execution environment image type¶
What the picker needs to query:
| Dimension | Description | Notes |
|---|---|---|
usableAsBase |
Filter to images that can act as a base image in an EE definition | Boolean facet — not all EEs are usable as base |
imageRef |
Fully qualified pull reference (registry/image:tag) |
Written into execution-environment.yml |
digest |
OCI digest (sha256:...) for reproducible pinning |
Optional but preferred for production definitions |
recommended |
Whether this is the RH-recommended base for new definitions | UI renders a "Recommended" badge; at most one true |
Write schema:
interface EEBaseImageRef {
imageRef: string; // written directly into execution-environment.yml
digest?: string; // sha256:... for reproducible pinning
}
Authorization: ansible.execution-environments.view (already declared)
4. Non-Negotiable: sourceId Stability¶
The source field in CollectionItem is stored inside execution-environment.yml files
committed to source control. If the Content Management team changes the opaque ID for a source across
PAH upgrades (e.g. during a re-registration), existing EE definitions silently reference
a source that no longer exists.
We need one of:
- A stability guarantee: source IDs are immutable once assigned. Decommissioned sources get a tombstone (still resolvable, marked deprecated) rather than a deletion.
- A migration event: an observable signal we can subscribe to when a source ID changes, so we can update stored EE definitions proactively.
This is the single hardest dependency between our teams. Everything else in this document is a fresh build; this one touches committed customer files.
5. Open Questions¶
| # | Question | Impact |
|---|---|---|
| Q1 | Is Collection.providedBy in the current AutomationContentClient the stable source ID, or a display label? Determines whether we can use it as the stored source field. |
Write schema stability |
| Q2 | Is collection/version data served from a live PAH query or from a Backstage Search index (collator)? Determines picker latency, offline behaviour, and whether search is live-typed or request-on-open. | UX + freshness model |
| Q3 | For usableAsBase — is this a computed field (introspection-derived) or manually curated? Affects whether new Red Hat EE releases appear automatically or require a catalog update. |
BaseImagePicker accuracy |
| Q4 | Is digest always available for catalog-indexed images, or only after a live registry fetch? |
Reproducibility guarantee |
6. What We Commit To (Portal team)¶
Once the open questions above are answered:
- Implementing
CollectionsPickeragainst the agreed query API — the cascade (name → source → version) maps to the declared query dimensions regardless of how the content service implements the underlying query. - Implementing
BaseImagePickerdynamic fetching against theusableAsBasefilter. - Publishing
CollectionItemandEEBaseImageRefinportal-extension-commonas the stable write schemas both teams import. - Not coupling picker internals to RHDH APIs. The picker calls our abstraction layer; that layer calls the published content client. When RHDH changes the search seam, we update the adapter — not the picker widget and not the template authors.
7. Non-Content Pickers (Out of Scope for This Contract)¶
The remaining five pickers have no content ownership dependency:
| Picker | Data source | Ownership after extraction |
|---|---|---|
EETagsPicker |
User-provided free text | Stays in portal-scaffolder |
PackagesPicker |
User-provided free text (pip package names) | Stays in portal-scaffolder |
MCPServersPicker |
Static schema enum from template YAML | Stays in portal-scaffolder |
AAResourcePicker |
AAP API (organizations, inventories, projects) | Stays in portal-scaffolder, continues to use AAP proxy |
ScmSelector |
Static enum (GitHub, GitLab) | Stays in portal-scaffolder; may add Gitea if the content service supports it |
The Content Management team is not blocked on any of these five. They are entirely portal-scaffolder owned and require no content boundary agreement.