Skip to content

Fix index.db namespace collisions and support divergent-tag builds - #1378

Open
yashvardhannanavati wants to merge 18 commits into
mainfrom
worktree-index-db-naming-divergent-tags
Open

Fix index.db namespace collisions and support divergent-tag builds#1378
yashvardhannanavati wants to merge 18 commits into
mainfrom
worktree-index-db-naming-divergent-tags

Conversation

@yashvardhannanavati

Copy link
Copy Markdown
Collaborator

Summary

Fixes two architectural collisions in the containerized (git + ORAS + Konflux) worker architecture, where the index.db artifact identity and the git branch were derived from keys that are too weak.

Problem 1 — namespace collision. The index.db ORAS-artifact and ImageStream tags derived from repo-name + OCP version only, dropping registry/namespace. So quay.io/redhat/foo and quay.io/redhat-pending/foo collapsed onto the same artifact reference and read/overwrote each other's index.db → corruption.

Problem 2 — divergent tags. The git branch was the OCP version read from image labels, so a divergent tag (:test, or a timestamped v4.14-<ts>) resolved to the same branch as mainline v4.14 and collided in git.

What changed

Part A — namespace-safe artifact naming (clean cutover). The artifact/ImageStream tag now includes a short 8-char sha256 hash of the full pullspec (registry + namespace + repo + tag): {image_name}-{tag}-{pullspec_hash}. Distinct namespaces now get distinct tags and never collide. No dual-read (which would reintroduce the bug) — the first request after deploy is a normal cache miss that repopulates from Quay. Old tags become orphaned and are pruned separately.

Part B — branch = image tag, with a normal/divergent discriminator. The git branch is now keyed on the image tag. For prod indexes tag == ocp_version, so this is a no-op for the existing fleet.

  • Tag with an existing remote branch → normal path (today's behavior). Overwrite allowed.
  • Tag with no branch → divergent path: extract configs/ + index.db from the image using the unprivileged extractor only (skopeo → OCI → untar; precedence hidden-db → labeled-db → empty db for pure-FBC images), reuse the base OCP branch's Konflux Component via a throw-away MR that is never merged, and reject overwrite. The divergent index.db is never sourced from ORAS. Timestamped/point-in-time tags stay throw-away by design.

Commits

  • fcdaadb fix: make index.db artifact tag namespace-safe via pullspec hash
  • 23e76df feat: add remote_branch_exists and get_index_tag helpers
  • 8128a7d feat: unprivileged extraction of configs+index.db from index image
  • 0c1f478 feat: prepare_build_sources orchestrates normal vs divergent build paths
  • e3f9dcb / 2d892dd / 6102d86 feat: route add / rm / fbc-operations through normal/divergent build sources
  • a1ed741 test: prove divergent never-merge guard with overwrite=True
  • 892cb25 docs: document branch=tag convention and divergent-tag builds

Scope

In scope: single-from_index request types — add, rm, fbc-operations. Out of scope (unchanged): merge-index-image, regenerate-bundle, create-empty-index (its empty-artifact ref is preserved byte-identical). Digest-referenced from_index is deferred. The old (non-containerized) worker handlers are untouched.

Type of change

  • Bug fix
  • New feature / request type
  • Documentation

Checklist

  • Unit tests added or updated (tox -e py312 → 1211 passed, 93.37% coverage)
  • All tests and linters pass (tox -m static: black / flake8 / yamllint / mypy all OK)
  • New Alembic migration created if models changed — N/A (no model changes)
  • API ↔ Worker task signatures kept in sync if task args changed
  • CHANGELOG.md updated — N/A (the Unreleased section is empty by convention; entries are generated from PR titles at release time)

🤖 Generated with Claude Code

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 30, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 11:06 AM UTC · Completed 11:24 AM UTC

Commit: 892cb25 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $11.00

@qodo-for-releng

Copy link
Copy Markdown

PR Summary by Qodo

Prevent index.db collisions and support divergent-tag builds

🐞 Bug fix ✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Namespaces index.db artifacts with full-pullspec hashes to prevent cross-registry cache
 corruption.
• Routes image-tag builds through onboarded branches or isolated divergent-tag sources.
• Prevents divergent requests from using ORAS inputs, overwriting tags, or merging MRs.
Diagram

graph TD
  A["Image tag"] --> B{"Branch exists?"}
  B -->|Yes| C["Tag branch"] --> D["Hashed ORAS cache"] --> G["Konflux build"] --> H["Finalize MR"]
  B -->|No| E["Base OCP branch"] --> F["Image extraction"] --> G
  B -. "divergent closes only" .-> H
Loading
High-Level Assessment

The split source-resolution approach is appropriate: normal tags preserve existing Git/ORAS behavior, while divergent tags use the image as their source of truth and remain throw-away. A legacy dual-read would reintroduce namespace ambiguity, and mandatory per-tag branch/Component provisioning would defeat one-off divergent builds.

Files changed (18) +975 / -132

Enhancement (5) +235 / -54
build_containerized_add.pyRoute add builds through resolved normal or divergent sources +20/-17

Route add builds through resolved normal or divergent sources

• Uses centralized build-source resolution, selecting an extracted database for divergent tags and ORAS for normal tags. Adds a defensive guard preventing divergent merge requests from merging.

iib/workers/tasks/build_containerized_add.py

build_containerized_fbc_operations.pySupport divergent sources in FBC operations +19/-16

Support divergent sources in FBC operations

• Routes FBC fragment operations through centralized source resolution. Divergent operations consume image-extracted index.db content and always close their merge requests.

iib/workers/tasks/build_containerized_fbc_operations.py

build_containerized_rm.pySupport divergent sources in removal builds +20/-17

Support divergent sources in removal builds

• Uses tag-aware source resolution for removal requests and bypasses ORAS when image content was extracted. Prevents divergent merge requests from being merged.

iib/workers/tasks/build_containerized_rm.py

containerized_utils.pyOrchestrate normal and divergent build inputs +164/-4

Orchestrate normal and divergent build inputs

• Introduces BuildSources and resolves builds by image-tag branch availability. Adds unprivileged catalog/database extraction with hidden-db, labeled-db, and pure-FBC fallback precedence, while applying hashed artifact names to request-specific pushes.

iib/workers/tasks/containerized_utils.py

git_utils.pyAdd non-raising remote branch detection +12/-0

Add non-raising remote branch detection

• Adds a git ls-remote helper that reports whether a named branch exists, enabling normal-versus-divergent routing.

iib/workers/tasks/git_utils.py

Bug fix (2) +33 / -19
build_containerized_create_empty_index.pyKeep empty-index bootstrap artifact lookup explicit +1/-2

Keep empty-index bootstrap artifact lookup explicit

• Builds the pre-existing empty index.db artifact tag directly instead of calling the newly pullspec-based combined-tag helper.

iib/workers/tasks/build_containerized_create_empty_index.py

oras_utils.pyMake index.db artifact identities namespace-safe +32/-17

Make index.db artifact identities namespace-safe

• Derives artifact and ImageStream tags from the repository name, image tag, and stable short hash of the full pullspec. Adds an image-tag parser helper and propagates the new identity through cache verification and refresh paths.

iib/workers/tasks/oras_utils.py

Tests (8) +675 / -58
test_build_containerized_add.pyCover add source routing and divergent merge protection +187/-18

Cover add source routing and divergent merge protection

• Updates add-handler fixtures for BuildSources and verifies divergent builds use extracted index.db content, never query ORAS, and never merge even when overwrite is forced past source validation.

tests/test_workers/test_tasks/test_build_containerized_add.py

test_build_containerized_create_empty_index.pyAlign empty-index tests with artifact helper changes +0/-9

Align empty-index tests with artifact helper changes

• Removes obsolete pullspec parsing mocks after empty-index artifact lookup no longer uses the combined-tag helper.

tests/test_workers/test_tasks/test_build_containerized_create_empty_index.py

test_build_containerized_fbc_operations.pyTest divergent FBC operation isolation +130/-3

Test divergent FBC operation isolation

• Adapts existing cases to tag-branch detection and adds coverage proving divergent FBC builds avoid ORAS and close rather than merge their merge requests.

tests/test_workers/test_tasks/test_build_containerized_fbc_operations.py

test_build_containerized_rm.pyTest tag-aware removal and divergent isolation +149/-19

Test tag-aware removal and divergent isolation

• Updates removal tests for branch detection and pullspec-based artifact helpers. Adds a regression case ensuring divergent removals use extracted index.db content and never merge.

tests/test_workers/test_tasks/test_build_containerized_rm.py

test_containerized_utils.pyTest image extraction and build-source resolution +152/-0

Test image extraction and build-source resolution

• Covers hidden database precedence, labeled database fallback, pure-FBC empty database creation, and missing config labels. Verifies normal branch selection, divergent extraction, and overwrite rejection.

tests/test_workers/test_tasks/test_containerized_utils.py

test_git_utils.pyTest remote branch existence checks +12/-0

Test remote branch existence checks

• Verifies populated and empty git ls-remote responses map to true and false branch-existence results.

tests/test_workers/test_tasks/test_git_utils.py

test_oras_utils.pyTest namespace-safe artifact tags and tag parsing +42/-8

Test namespace-safe artifact tags and tag parsing

• Updates helper tests to pass full pullspecs and validates namespace separation, stable eight-character hashes, and image-tag extraction.

tests/test_workers/test_tasks/test_oras_utils.py

test_utils.pyProvide artifact template context in cache tests +3/-1

Provide artifact template context in cache tests

• Updates cache verification tests to mock worker artifact-tag configuration required by full-pullspec tag generation.

tests/test_workers/test_tasks/test_utils.py

Documentation (2) +31 / -0
AGENTS.mdRecord divergent-build safety invariants +1/-0

Record divergent-build safety invariants

• Documents that divergent-tag merge requests must never merge and their index.db must never come from ORAS. Identifies the handler-level merge guard reviewers must preserve.

AGENTS.md

README.mdDocument tag branches, divergent builds, and cache naming +30/-0

Document tag branches, divergent builds, and cache naming

• Explains image-tag branch semantics, onboarding requirements, and the divergent-tag workflow. Documents namespace-safe artifact tags and the intentional clean cache cutover.

docker/containerized/README.md

Other (1) +1 / -1
config.pyAdd pullspec hash to artifact tag template +1/-1

Add pullspec hash to artifact tag template

• Extends the configured index.db artifact identity with an eight-character hash placeholder derived from the full pullspec.

iib/workers/config.py

@qodo-for-releng

qodo-for-releng Bot commented Aug 30, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Whiteouts leave deleted catalog files ✓ Resolved 🐞 Bug ≡ Correctness
Description
The divergent path now sources configs and databases through an extractor that overlays layer
tarballs with extractall but never applies OCI whiteout or opaque-directory entries. Files deleted
by later image layers remain in the reconstructed catalog, so divergent builds can reintroduce
operators or database content that is absent from the actual source image.
Code

iib/workers/tasks/containerized_utils.py[R171-172]

+    configs_dir = str(Path(temp_dir) / 'extracted_configs')
+    extract_files_from_image_non_privileged(from_index, configs_label, configs_dir)
Relevance

●●● Strong

Whiteout handling is a concrete correctness gap in layer reconstruction and directly affects
divergent catalog fidelity.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Layer archives are simply extracted in sequence, with no code that recognizes .wh.* entries or
removes lower-layer paths; the new divergent path then copies configs and index.db directly from
this reconstructed tree.

iib/workers/tasks/containerized_utils.py[103-122]
iib/workers/tasks/containerized_utils.py[124-144]
iib/workers/tasks/containerized_utils.py[149-187]
iib/workers/tasks/containerized_utils.py[642-647]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The newly activated image-content source reconstructs OCI filesystems without processing whiteouts, retaining files deleted in later layers.

## Issue Context
Apply layers with OCI whiteout and opaque-directory handling, or use an unprivileged OCI unpack implementation that produces the final root filesystem before copying configs and index.db.

## Fix Focus Areas
- iib/workers/tasks/containerized_utils.py[107-144]
- iib/workers/tasks/containerized_utils.py[171-187]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Private branches appear divergent ✓ Resolved 🐞 Bug ≡ Correctness
Description
remote_branch_exists checks the tokenless repository URL with non-strict git ls-remote, so
private-repository authentication failures and other command failures return the same empty output
as a missing branch. Existing onboarded branches can therefore be classified as divergent, causing
overwrite requests to be rejected and normal requests to fail the base-branch check.
Code

iib/workers/tasks/git_utils.py[R176-177]

+    remote_branch_status = run_cmd(["git", "ls-remote", "--heads", repo_url, branch], strict=False)
+    return bool(remote_branch_status.strip())
Relevance

●●● Strong

Suppressing authenticated ls-remote failures as branch absence can misclassify private repositories
and alter build behavior.

PR-#1126

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new helper suppresses command failure handling, while the repository's clone path explicitly
requires credentials; both failed lookups and absent branches therefore become False and alter
source-path selection.

iib/workers/tasks/git_utils.py[168-177]
iib/workers/tasks/utils.py[991-1026]
iib/workers/tasks/git_utils.py[239-259]
iib/workers/tasks/git_utils.py[278-300]
iib/workers/tasks/containerized_utils.py[599-632]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Branch existence checks use an unauthenticated URL and suppress `git ls-remote` failures, so existing private branches can be treated as missing.

## Issue Context
`prepare_build_sources` already retrieves the Git token, and `clone_git_repo` injects it into the remote URL. The existence check should use equivalent authentication and distinguish a successful empty result from a failed command.

## Fix Focus Areas
- iib/workers/tasks/git_utils.py[168-177]
- iib/workers/tasks/containerized_utils.py[599-604]
- iib/workers/tasks/containerized_utils.py[631-632]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Extraction failures become absence ✓ Resolved 🐞 Bug ☼ Reliability
Description
extract_catalog_and_db_from_image catches every IIBError from hidden-database extraction and
treats it as “not present,” including registry, OCI metadata, missing-layer, and tar failures. The
build can then silently use the labeled database or manufacture an empty file, losing hidden bundle
state instead of failing the request.
Code

iib/workers/tasks/containerized_utils.py[R180-183]

+        extract_files_from_image_non_privileged(from_index, hidden_db_path, index_db_path)
+        return configs_dir, index_db_path
+    except IIBError:
+        log.info("No hidden index.db in %s; trying labeled db.", from_index)
Relevance

●●● Strong

Catching identical errors for absence and extraction failures can silently discard state; separating
these outcomes is a clear reliability fix.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The extractor raises the same IIBError type for failures throughout download/unpack and for a
genuinely absent path, while the new caller catches that broad type and proceeds to lower-precedence
data or a zero-byte fallback.

iib/workers/tasks/containerized_utils.py[70-101]
iib/workers/tasks/containerized_utils.py[107-133]
iib/workers/tasks/containerized_utils.py[178-200]
iib/workers/tasks/containerized_utils.py[642-655]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
All hidden-database extraction errors are interpreted as a missing path, masking real image download and unpack failures.

## Issue Context
Introduce a distinct missing-path result/exception or inspect path existence separately. Propagate all transport, OCI parsing, layer, and tar errors.

## Fix Focus Areas
- iib/workers/tasks/containerized_utils.py[51-62]
- iib/workers/tasks/containerized_utils.py[128-133]
- iib/workers/tasks/containerized_utils.py[178-198]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View high (1)
4. Mutable tag extraction races ✓ Resolved 🐞 Bug ☼ Reliability
Description
prepare_build_sources extracts divergent content from the original tagged from_index even though
each handler already resolved that image to a digest during prebuild. If the tag moves, labels,
configs, and databases can come from a different image than the one used to select the OPM version
and record build metadata, and the repeated image reads can even disagree with each other.
Code

iib/workers/tasks/containerized_utils.py[R642-643]

+    # Content from the image (source of truth), scaffolding from the OCP branch.
+    extracted_configs, extracted_db = extract_catalog_and_db_from_image(from_index, temp_dir)
Relevance

●● Moderate

Digest pinning is a strong consistency concern, but historical evidence does not establish the
team’s response to this architectural change.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
All three handlers have from_index_resolved available and use it for OPM selection or metadata,
but pass the unresolved input into the new source preparer; that preparer performs multiple label
and skopeo reads using the mutable input.

iib/workers/tasks/build_containerized_add.py[131-145]
iib/workers/tasks/build_containerized_add.py[157-169]
iib/workers/tasks/containerized_utils.py[167-187]
iib/workers/tasks/containerized_utils.py[642-643]
iib/workers/tasks/utils.py[1285-1298]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Divergent extraction repeatedly reads a mutable image tag instead of the digest already resolved by request preparation.

## Issue Context
Keep the original pullspec for mapping and tag-based branch selection, but pass the resolved pullspec separately as the content source for label inspection and extraction.

## Fix Focus Areas
- iib/workers/tasks/containerized_utils.py[566-573]
- iib/workers/tasks/containerized_utils.py[642-643]
- iib/workers/tasks/build_containerized_add.py[163-170]
- iib/workers/tasks/build_containerized_rm.py[137-144]
- iib/workers/tasks/build_containerized_fbc_operations.py[134-141]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can show, collapse, or hide each part of a finding: code, evidence, and all

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread iib/workers/tasks/git_utils.py Outdated
Comment thread iib/workers/tasks/containerized_utils.py Outdated
Comment thread iib/workers/tasks/containerized_utils.py Outdated
Comment thread iib/workers/tasks/containerized_utils.py Outdated
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 30, 2026

Copy link
Copy Markdown

Review

Findings

High

  • [scope-creep] docker/containerized/README.md:287 — The content-key migration orphans every existing ORAS-cached index.db artifact in production. The README explicitly documents this: cached entries under the previous naming scheme are orphaned, and affected images fail with “no index.db found” until re-onboarded. Every onboarded image will begin failing immediately after deployment with no fallback path or migration mechanism.
    Remediation: Add an explicit deployment checklist item, or introduce a transient fallback that checks the digest-keyed artifact and, if absent, falls back to the old pullspec-keyed artifact for one release cycle.

  • [protected-path] AGENTS.md — This PR modifies the governance file AGENTS.md (a protected path). The PR has no linked issue providing authorization for the change. Human approval is required for all protected-path modifications.

Medium

  • [API-contract-violation] iib/workers/tasks/containerized_utils.py:501push_index_db_artifact hardcodes the artifact tag format as f'idb-{_get_index_digest(output_image)}' instead of using the configurable iib_index_db_artifact_tag_template from the worker config. The lookup path in _get_artifact_combined_tag (oras_utils.py:96) correctly uses the config template. If the template is overridden from the default 'idb-{digest}', pushed tags will not match lookup tags, causing cache misses or lookup failures.
    Remediation: Replace the hardcoded tag with the config template: output_tag = conf['iib_index_db_artifact_tag_template'].format(digest=_get_index_digest(output_image)).

  • [path-traversal] iib/workers/tasks/containerized_utils.py:690 — On the divergent-tag path, shutil.copytree(extracted_configs, catalog_path) uses the default symlinks=False, which follows symlinks on the host filesystem. The upstream extract_files_from_image_non_privileged (line 121) explicitly uses symlinks=True to preserve symlinks from oc image extract. A malicious from_index image embedding an absolute symlink in its /configs directory could exfiltrate host-local files into the git-committed catalog_path.
    Remediation: Pass symlinks=True to the shutil.copytree call at line 690, or add a post-extraction pass that detects and removes symlinks targeting paths outside the temp directory.

  • [missing-authorization] iib/workers/tasks/containerized_utils.py — No linked issue for a non-trivial structural change. The PR introduces a new execution path (BuildSources dataclass, prepare_build_sources), rewrites the ORAS artifact key, removes the artifact rollback path in cleanup_on_failure, and replaces skopeo-based tarfile extraction with oc-image-extract.
    Remediation: Open and link a GitHub issue capturing the two bugs (namespace collision, divergent-tag collision), the agreed fix strategy, and the operational impact.

  • [breaking-config-interface] iib/workers/config.py:74 — The format variable names in iib_index_db_artifact_tag_template changed from {image_name} and {tag} to {digest}. Any deployment overriding this setting in a site-level settings.py will get an unhandled KeyError at runtime when _get_artifact_combined_tag calls .format(digest=...) on a template referencing {image_name} and {tag}.
    Remediation: Validate the config key’s format string at startup (check that {digest} is present, warn if {image_name}/{tag} are found), or document that the template format is not operator-configurable.

Low

  • [secret-exposure] iib/workers/tasks/git_utils.py:202 — The new remote_branch_exists function injects credentials into the git URL. The error-path log.error in run_cmd (utils.py line 923) logs the raw command without sanitization. Pre-existing pattern, but adds another affected call site.
    Remediation: Fix run_cmd to use _sanitize_cmd_log in the log.error call at utils.py line 923.

  • [naming-abstraction] iib/workers/tasks/containerized_utils.py:702prepare_git_repository_for_build is retained alongside the new prepare_build_sources for request types where divergent-tag detection does not apply (merge, create_empty_index), but lacks any comment explaining why it was not migrated.
    Remediation: Add a docstring note stating it is intentionally retained for request types where divergent-tag detection does not apply.

  • [documentation-comment-format] iib/workers/tasks/git_utils.py:190remote_branch_exists uses :rtype: bool with no :return: description, diverging from the file’s established :return: <description> convention.
    Remediation: Replace :rtype: bool with :return: True if the branch exists on the remote, False otherwise.

  • [documentation-comment-format] iib/workers/tasks/oras_utils.py:66 — Three new functions use :return: + :rtype: on separate lines, while pre-existing functions use :returns Type: description inline. Introduces a mixed docstring style.
    Remediation: Adopt one consistent style across all functions in the file.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run

Review

Findings

High

  • [protected-path] AGENTS.md — This PR modifies a governance/infrastructure file (AGENTS.md) without a linked issue authorizing the change. Human approval is required for all protected-path changes regardless of context.
    Remediation: Link a tracking issue that authorizes modifications to AGENTS.md, or obtain explicit maintainer approval for the protected-path change.

Medium

  • [scope-creep] docker/containerized/README.md:49 — The artifact rename from {image_name}-{tag} to idb-{digest} orphans every existing cached index.db artifact in production. The README documents this and explains the mitigation (cache-pruning cleans up orphans; missing artifacts cause an explicit error), but the one-time re-onboarding churn should be called out in release notes or an operational runbook.
    Remediation: Document the expected one-time onboarding churn in release notes or an operational runbook alongside the PR.

  • [error-handling] iib/workers/tasks/containerized_utils.py:391pull_index_db_artifact catches all IIBError from get_oras_artifact and re-raises with a user-facing "No index.db found ... Onboard the image to build" message. This masks infrastructure errors (network timeout, authentication failure) as "not found." The raise does not chain the exception (no from clause), so the original traceback is lost to outer handlers.
    Remediation: Use raise IIBError(...) from e to preserve the original exception chain, or narrow the catch to distinguish "artifact not found" from infrastructure failure.

  • [architectural-coherence] iib/workers/tasks/build_containerized_merge.py:26 — The merge handler still imports and uses prepare_git_repository_for_build while add, rm, and fbc_operations handlers all use the new prepare_build_sources / BuildSources abstraction. This creates an asymmetry: the merge handler has no divergent-tag support and no overwrite guard. prepare_git_repository_for_build is preserved alongside prepare_build_sources without documentation marking it as merge-handler-only.
    Remediation: Either migrate the merge handler to prepare_build_sources (with divergent-tag support disabled or documented as unsupported), or add a docstring to prepare_git_repository_for_build marking it as merge-handler-only.

Low

  • [symlink-escape] iib/workers/tasks/containerized_utils.py:121extract_files_from_image_non_privileged uses shutil.copytree with symlinks=True at lines 121 and 176. Malicious images could contain symbolic links pointing to absolute paths on the host. Practical exploitability is limited because oc image extract applies OCI-level sanitization and the destination is a temp directory.
    Remediation: Use symlinks=False or validate symlink targets after extraction.

  • [config-migration] iib/workers/config.py:74 — The iib_index_db_artifact_tag_template default changed from {image_name}-{tag} to idb-{digest}. Deployments customizing this config with old placeholders will get an immediate KeyError at runtime when .format(digest=...) is called — a loud crash, not silent corruption.
    Remediation: Add a startup validation check that the template contains {digest}, or document the config migration in release notes.

  • [docstring-convention] iib/exceptions.py:16 — All existing exception classes use single-sentence docstrings. FileNotFoundInImageError introduces a multi-paragraph rationale docstring, deviating from the established convention.
    Remediation: Collapse to a single sentence; move rationale to an inline comment.

  • [error-handling] iib/workers/tasks/containerized_utils.py:252extract_catalog_and_db_from_image catches FileNotFoundInImageError and raises a new IIBError without chaining (from clause). Original traceback is lost.

  • [image-label-path] iib/workers/tasks/containerized_utils.py:237extract_catalog_and_db_from_image reads configs_label from an untrusted index image and passes it to extraction as src_path. Validates it starts with / but does not restrict further. Extracted content comes from the image's own layers.

  • [edge-case] iib/workers/tasks/git_utils.py:202remote_branch_exists uses git ls-remote --heads where the branch argument is treated as a refspec pattern by git. Glob characters in tags could match multiple branches; in practice, image tags do not contain glob characters.

  • [naming-alignment] iib/workers/tasks/oras_utils.py:84_get_artifact_combined_tag name still says "combined" but it now produces a content-addressed idb-<digest> tag. Name no longer reflects behavior.
    Remediation: Rename to _get_content_addressed_artifact_tag or _get_digest_artifact_tag.

  • [code-organization] iib/workers/tasks/containerized_utils.py:88 — Function-scoped import of run_cmd inside extract_files_from_image_non_privileged while the module already imports from the same iib.workers.tasks.utils at the top level.
    Remediation: Move run_cmd into the module-level import.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (2)

Review

Findings

High

  • [protected-path] AGENTS.md — PR modifies a protected governance file (AGENTS.md) without a linked issue providing authorization. Human approval is required for all protected-path changes regardless of context.

  • [missing-authorization] — No linked GitHub issue for a ~2049-line, 21-file architectural change that introduces new build paths (divergent-tag), extraction primitives, branch selection semantics, and alters the artifact cache key globally. The PR description provides rationale, but non-trivial changes of this scope require traceable authorization.
    Remediation: Link this PR to a GitHub issue documenting the problem statement and approved solution approach.

  • [pr-description-code-mismatch] — The PR description’s Part A claims the artifact tag uses “a short 8-char sha256 hash of the full pullspec” with format {image_name}-{tag}-{pullspec_hash}, but the actual implementation uses the full 64-char manifest digest with format idb-{digest} (see config.py:74, oras_utils.py:_get_artifact_combined_tag). The in-repo documentation (docker/containerized/README.md) correctly describes the manifest-digest approach.
    Remediation: Update the PR description’s Part A to match the actual implementation.

Medium

  • [error handling / silent degradation] iib/workers/tasks/containerized_utils.py:386pull_index_db_artifact catches bare IIBError when the ORAS pull fails, then falls back to bootstrap_index_db_from_image. This catch is overly broad — IIBError covers network errors, auth failures, and malformed manifests, not just “artifact not found.” A transient registry outage would silently trigger a full re-extraction and push a potentially stale index.db to the cache. The PR already introduces the FileNotFoundInImageError subclass pattern for this exact purpose.
    Remediation: Introduce a subclass (e.g., ArtifactNotFoundError) for missing ORAS artifacts and catch only that in the bootstrap fallback.

  • [scope-creep] — The PR conflates a bug fix (Part A: digest-based artifact keys fixing namespace collisions) with a new feature (Part B: divergent-tag builds with branch=tag semantics, unprivileged image extraction, throw-away MR support). These are architecturally related but represent different authorization tiers.

Low

  • [race condition / TOCTOU] iib/workers/tasks/containerized_utils.py:638remote_branch_exists and clone_git_repo are not atomic. A narrow TOCTOU window exists, though the worst case is a clean IIBError or a functionally correct divergent path.

  • [API contract] iib/workers/tasks/containerized_utils.py:495push_index_db_artifact calls _get_index_digest(output_image) (a skopeo inspect against the registry) at a late stage after the pipeline has completed. A failure here leaves a partially-completed state, though the net risk is comparable to the old code’s get_image_digest call.

  • [empty file as SQLite database] iib/workers/tasks/containerized_utils.py:216 — The pure-FBC fallback creates a zero-byte file via open(path, 'w'): pass, which is not a valid SQLite database (SQLite requires a 100-byte header). Downstream opm operations may reject it.
    Remediation: Use sqlite3.connect(index_db_path).close() to create a valid empty SQLite database.

  • [token-leakage] iib/workers/tasks/git_utils.pyremote_branch_exists injects credentials into the git URL. The exc_msg correctly references the token-free URL, and the pattern is consistent with existing clone_git_repo. Minimal risk.

  • [cache-population] iib/workers/tasks/containerized_utils.py:305bootstrap_index_db_from_image unconditionally pushes to ORAS on any cache miss. Content-addressed keys prevent overwriting other entries, but any request triggering a miss populates the cache.

  • [mutable-dataclass] iib/workers/tasks/containerized_utils.py:581BuildSources is a plain @dataclass (mutable). Adding frozen=True would prevent accidental mutation of the is_divergent guard flag as defense-in-depth.
    Remediation: Add frozen=True to the @dataclass decorator.

  • [cache-orphaning] iib/workers/config.py:74 — The artifact tag template change from {image_name}-{tag} to idb-{digest} orphans all pre-existing cached artifacts. The README documents the read-through bootstrap fallback, but an ops deployment note about cache warming for high-traffic indexes may be helpful.

  • [docstring-style] iib/exceptions.py:63FileNotFoundInImageError uses a multi-line docstring while other exceptions in the file use single-line docstrings.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (3)

Review

Findings

High

  • [protected-path] AGENTS.md:60 — AGENTS.md is a protected governance file. This PR modifies it to document the divergent-tag invariant but has no linked issue authorizing changes to protected paths. Human approval is always required for protected-path changes regardless of context.
    Remediation: Link an issue authorizing the AGENTS.md change, or obtain explicit human approval for the protected-path modification.

Medium

  • [scope-creep] PR Fix index.db namespace collisions and support divergent-tag builds #1378 — PR bundles two distinct changes: (1) namespace collision bug fix (artifact naming via pullspec hash), and (2) new divergent-tag feature (branch-less builds with ~400 lines of new logic including BuildSources, prepare_build_sources, extract_catalog_and_db_from_image, remote_branch_exists). The changes share implementation surface but could be reviewed independently for clearer scope.

  • [breaking-config-change] iib/workers/config.py:74 — Default iib_index_db_artifact_tag_template changed from {image_name}-{tag} to {image_name}-{tag}-{pullspec_hash}, orphaning all existing cached index.db artifacts on upgrade. Documented in docker/containerized/README.md as cleaned up by existing cache-pruning, but operators should be aware of the first-request cache miss impact after deployment.
    Remediation: Consider documenting the expected cache-miss window for operators upgrading existing deployments.

  • [architectural-coherence] iib/workers/tasks/oras_utils.py:75 — Artifact naming scheme creates a clean cutover with no dual-read or migration period. Old cache entries are orphaned and rely on existing pruning. Operators may benefit from explicit migration guidance beyond the containerized README note.
    Remediation: Add a note in the PR description or deployment runbook about the expected post-upgrade cache behavior.

Low

  • [missing-authorization] No linked issue found for this non-trivial PR with architectural changes across 19 files (~1,276 changed lines).

  • [edge-case] iib/workers/tasks/containerized_utils.py:460 — On the divergent path, push_index_db_artifact still runs but only creates a per-request tag (since overwrite_from_index is always False on this path due to the prepare_build_sources guard). These are standard audit artifacts, not orphaned entries.

  • [secrets-handling] iib/workers/tasks/git_utils.py:196remote_branch_exists embeds credentials in the git URL, following the same pre-existing pattern as clone_git_repo. The exc_msg correctly references the token-free repo_url to avoid leaking secrets in error messages.

  • [data-integrity] iib/workers/tasks/build_containerized_create_empty_index.py:207 — Empty index.db artifact tag intentionally omits the pullspec hash (shared, namespace-agnostic seed). Documented design decision; no data integrity risk for content-free artifacts.

  • [toctou] iib/workers/tasks/containerized_utils.py:629remote_branch_exists to clone_git_repo has a TOCTOU window. Neither race scenario leads to data corruption.

  • [hash-collision] iib/workers/tasks/oras_utils.py:76 — 8-char SHA-256 truncation (32 bits) has a birthday bound of ~65,536 distinct pullspecs. Adequate for the current fleet size.

  • [naming-alignment] iib/workers/tasks/containerized_utils.py:687 — Old prepare_git_repository_for_build (tuple return) coexists with new prepare_build_sources (BuildSources dataclass). The old function is still used by create_empty_index.

  • [docstring-consistency] iib/workers/tasks/oras_utils.py:74_get_pullspec_hash has a single-line docstring missing the standard :param/:rtype: documentation per project convention.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (4)

Review

Findings

High

  • [protected-path] AGENTS.md — This PR modifies AGENTS.md, which is a protected governance file. The PR has no linked issue providing justification for modifying protected infrastructure files. Human approval is required for all protected-path changes.
    Remediation: Link an issue that explicitly authorizes modifications to AGENTS.md, or ensure a human reviewer approves the protected-path change.

Medium

  • [logic-error] iib/workers/tasks/git_utils.py:173remote_branch_exists uses git ls-remote --heads with strict=False. If the git command fails (network error, auth failure), run_cmd returns an empty string (confirmed: when strict=False and returncode != 0, run_cmd falls through to return response.stdout). Empty stdout is falsy, so the function incorrectly reports the branch does not exist. This silently routes prepare_build_sources onto the divergent path, extracting content from the image instead of cloning from git, and rejecting overwrite_from_index unnecessarily.
    Remediation: Distinguish "branch absent" from "command failed" — use strict=True with try/except, or check the return code separately.

  • [API-shape-inconsistency] iib/workers/tasks/build_containerized_create_empty_index.py:202 — The create_empty_index handler bypasses _get_artifact_combined_tag by inlining f"{image_name}-{empty_tag}". This is intentionally different (the empty artifact is a shared, namespace-agnostic sentinel), but the code lacks an explanatory comment. A future maintainer could "fix" this and break the shared semantics.
    Remediation: Add an inline comment explaining that the empty-tag artifact is intentionally shared across namespaces and should not include the pullspec hash.

Low

  • [glob-pattern-matching] iib/workers/tasks/git_utils.py:176git ls-remote --heads treats the branch argument as a glob pattern. Glob metacharacters (*, ?, [) in the tag would match unintended branches. Practically low risk since OCI registries reject tags with these characters, but the code does not validate this assumption.

  • [scope-creep] AGENTS.md:60 — The PR adds a project-level invariant rule to AGENTS.md that encodes the design decisions of the feature being introduced in this same PR. While documenting a feature alongside its implementation is reasonable, the rule should be reviewed independently as it establishes architectural precedent.

  • [scope-mismatch] — The PR bundles two related but distinct concerns (namespace collision fix + divergent-tag feature). The PR body explains the relationship, but splitting would allow independent review and bisection.

  • [test-inadequate] tests/test_workers/test_tasks/test_oras_utils.py:667test_get_artifact_combined_tag uses a config template '{image_name}-{tag}' without {pullspec_hash}, not exercising the production default. The production template IS tested in test_combined_tag_includes_namespace_hash, but a comment noting the intentional template difference would clarify the test design.

  • [naming-convention] iib/workers/tasks/oras_utils.py:63get_index_tag is public (no underscore prefix) but only used internally by containerized_utils.py. Consider making it private (_get_index_tag).

  • [code-duplication] iib/workers/tasks/build_containerized_add.py:112 — The BuildSources field unpacking + index-db-source selection pattern is duplicated across three handler files (add, rm, fbc_operations). Minor DRY concern — the pattern is only 7 lines.

  • [edge-case] iib/workers/tasks/containerized_utils.py:197 — For pure-FBC images, creates a 0-byte file (not a valid SQLite database). Relies on opm initializing from scratch. Matches the existing _get_or_create_temp_index_db_file pattern.

  • [hash-collision] iib/workers/tasks/oras_utils.py:82 — 8-char SHA256 prefix (32 bits) for namespace disambiguation. Birthday collision probable at ~65K pullspecs. Practically low risk for typical deployments, but a collision would silently share index.db between namespaces.

  • [breaking-change-communication] iib/workers/config.py:74 — The default iib_index_db_artifact_tag_template changes from '{image_name}-{tag}' to '{image_name}-{tag}-{pullspec_hash}'. All existing cached artifacts are orphaned on deploy. The PR body and README acknowledge this, but deployment runbook/changelog may need updating.

  • [error-handling-idiom] iib/workers/tasks/git_utils.py:173remote_branch_exists and validate_git_remote_branch contain identical git ls-remote logic. validate_git_remote_branch could delegate to remote_branch_exists.

  • [docstring-convention] iib/workers/tasks/oras_utils.py:68_get_pullspec_hash has a one-line docstring without Sphinx annotations (:param/:return:/:rtype:). get_index_tag declares :raises IIBError: but doesn't raise directly.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR

fullsend-ai-review[bot]

This comment was marked as outdated.

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 1, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:26 AM UTC · Completed 1:45 AM UTC

Commit: bddca65 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $9.84

@fullsend-ai-review fullsend-ai-review Bot added the risk/moderate PR risk: moderate label Sep 1, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 1, 2026

Copy link
Copy Markdown

Risk Assessment: moderate (2/5)

Details

Signals are essentially unchanged from the prior assessment (21 files, ~2,517 lines, 0.38 test ratio, no security or CI changes, low 30d churn, stable file history), yielding the same composite of ~1.90, which rounds to 2 - moderate.

Previous run

Risk Assessment: moderate (2/5)

Details

A large refactor across 21 files with 2,525 lines changed drives a high change-size signal, but adequate test coverage (38% test ratio), zero security or CI changes, low recent churn, and no regression history across primarily stable files produce a composite score of 1.95, rounding to moderate.

Previous run (2)

Risk Assessment: moderate (2/5)

Details

Large change with 2049 lines across 21 files, but mitigated by stable git history (no recent fixes), reasonable test coverage (38%), and experienced author.

Previous run (3)

Risk Assessment: moderate (2/5)

Details

Large PR (19 files, 1253 lines) with one protected path and moderate test coverage (42%), mostly new files with stable existing files showing low churn and long stability (avg 236 days since last change).

fullsend-ai-review[bot]

This comment was marked as outdated.

log.info("No hidden index.db in %s; trying labeled db.", from_index_resolved)

db_label = get_image_label(
from_index_resolved, 'operators.operatorframework.io.index.database.v1'

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.

This means that the index image is not FBC index image and I thought that we do not support non-FBC images in new IIB.

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.

There should not exist an image with both LABELs set.
It is either this operators.operatorframework.io.index.database.v1 or this operators.operatorframework.io.index.configs.v1

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.

Especially when we have this code above:

   configs_label = get_image_label(
        from_index_resolved, 'operators.operatorframework.io.index.configs.v1'
    )
    if not configs_label:
        raise IIBError(f"Index image {from_index_resolved} does not contain a file-based catalog.")

Then db_label will never be filleted. It will be always empty.

# Content from the image (source of truth), scaffolding from the OCP branch.
# Extract from the digest-resolved pullspec, not the mutable tag.
extracted_configs, extracted_db = extract_catalog_and_db_from_image(
from_index_resolved, temp_dir

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.

I thought we do not extract db from index image anymore.
I think this will now work anymore, since we do not include index.db in the image we are building in konflux.

yashvardhannanavati and others added 15 commits September 2, 2026 00:13
Derive the index.db artifact and ImageStream tag from the index image's
manifest digest (idb-<sha256>) instead of the pullspec string. Different
content never collides; identical content addressed by different pullspecs
(e.g. a released mirror) shares one artifact.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Resolve three correctness issues raised by automated reviewers on the
index.db naming / divergent-tag work:

1. remote_branch_exists no longer runs git ls-remote with strict=False,
   which swallowed network/auth failures as "branch absent" and could
   misroute a request onto the divergent build path. It now raises on
   command failure and accepts optional token args so the check works
   against private repos (auth injected into the URL, kept out of errors).

2. Divergent extraction now sources index.db/configs from the
   digest-resolved pullspec (from_index_resolved) instead of the mutable
   tag, so the extracted content matches the image the request already
   inspected during prebuild and cannot drift if the tag moves.

3. extract_catalog_and_db_from_image only falls back to the labeled db /
   empty db when the hidden-db path is genuinely absent. A new
   FileNotFoundInImageError (subclass of IIBError) distinguishes an absent
   path from a real registry/OCI/layer/tar failure, which now propagates
   instead of silently degrading to a wrong index.db.

Also add clarifying comments: the empty index.db artifact tag is
intentionally namespace-agnostic, test_get_artifact_combined_tag
intentionally omits the pullspec hash, and drop a misleading
:raises IIBError: from get_index_tag's docstring.

Additionally, update stale test expectations uncovered by rebasing this
work onto the digest-identity base (a4ed53b): test_utils.py carried
duplicate copies of the oras_utils pullspec tests still asserting the
old {image_name}-{tag} naming without mocking get_image_digest, and
test_build_containerized_rm.py mocked
containerized_utils._get_name_and_tag_from_pullspec, a symbol the
digest-identity refactor removed from that module. Both are
test-expectation fixes only; no production code changed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Also updates build_containerized_merge.py and
build_containerized_regenerate_bundle.py, which call the same
push_index_db_artifact/cleanup_on_failure interfaces changed in Task 5/6
but were not enumerated in the task-7 brief; mypy caught the now-invalid
call signatures.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Update the containerized README's cache-naming section and an inline
comment to reflect the content-digest identity scheme (replacing the
earlier pullspec-hash description).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@yashvardhannanavati
yashvardhannanavati force-pushed the worktree-index-db-naming-divergent-tags branch from bddca65 to b73a69f Compare September 2, 2026 08:45
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 2, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:46 AM UTC · Completed 9:25 AM UTC

Commit: b73a69f · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $10.47

fullsend-ai-review[bot]

This comment was marked as outdated.

fullsend-ai-review[bot]

This comment was marked as outdated.

extract_catalog_and_db_from_image now extracts only the FBC configs and the
hidden index.db. Drop the labeled-db and synthesised empty-db fallbacks: an
image with no hidden index.db has not been onboarded to the containerized
flow, so the request fails ("no index.db found, onboard the image to build").

pull_index_db_artifact no longer bootstraps index.db from the image on an ORAS
miss. On the normal path a missing digest-keyed artifact fails the request
("no index.db found for the image, onboard the image to build") rather than
silently sourcing content from the image.

Remove the now-dead bootstrap_index_db_from_image helper and unused imports,
add source/destination path logging to aid debugging, and update tests and
the containerized README to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 4, 2026

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure (validation failed after 2 iteration(s)) · Started 12:07 AM UTC · Completed 12:49 AM UTC

Commit: b3210b7 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $10.83

extract_files_from_image_non_privileged reconstructed the entire root
filesystem with tarfile.extractall to copy out one subpath. That fails on
real UBI/RHEL images: the 'data' filter rejects absolute symlinks
(/etc/alternatives, AbsoluteLinkError), 'tar' rejects escaping symlink
targets (/etc/crypto-policies, OutsideDestinationError), and
'fully_trusted' trips on read-only files overwritten across layers
(/etc/machine-id) -- plus tarfile ignores OCI whiteouts and hardlinks.

Delegate to 'oc image extract' instead, which applies OCI layer/whiteout
semantics correctly, runs unprivileged, and is already installed in the
worker base image and used elsewhere in the repo. The function signature
is unchanged, so callers are unaffected.

'oc' distinguishes files from directories by argument shape and exits 0
extracting nothing when a path is absent, so probe the directory form
('<dir>/*:<dst>') first, then the file form ('<file>:<dst>'), and treat
"nothing extracted" as FileNotFoundInImageError.

Tests rewritten to mock 'oc'/run_cmd instead of tarfile/skopeo internals.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 4, 2026

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure (validation failed after 2 iteration(s)) · Started 9:20 AM UTC · Completed 10:02 AM UTC

Commit: 5124112 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 6, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:19 AM UTC · Completed 4:40 AM UTC

Commit: ab758de · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $10.83

fullsend-ai-review[bot]

This comment was marked as outdated.

fullsend-ai-review[bot]

This comment was marked as outdated.

'oc image extract' unpacks only file entries, so an empty index's /configs
(no files) is indistinguishable from an absent path — both raise
FileNotFoundInImageError. Rather than guess in the low-level extractor (the
reverted dotfile approach), let extract_catalog_and_db_from_image decide: it
holds the configs label as the signal that the image declares an FBC root, so
it catches FileNotFoundInImageError and uses an empty catalog directory. The
hidden index.db remains required and still fails with an onboarding error when
absent, regardless of whether /configs is empty.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@yashvardhannanavati
yashvardhannanavati force-pushed the worktree-index-db-naming-divergent-tags branch from ab758de to bde9a0c Compare September 6, 2026 08:12
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 6, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:14 AM UTC · Completed 8:36 AM UTC

Commit: bde9a0c · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $11.76

@fullsend-ai-review fullsend-ai-review Bot 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.

See the review comment for full details.

- **Namespace-safe:** two images that share a repository name in different registry namespaces (e.g. `quay.io/redhat/my-index:v4.17` vs `quay.io/redhat-pending/my-index:v4.17`) have different content and therefore different digests, so they never collide on the same cache tag.
- **Promotion-safe:** the same image content addressed by different pullspecs after a release or mirror (e.g. `quay.io/my-namespace/iib-pub:v4.17` → `registry.access.redhat.com/some-namespace/operator-index:v4.17`) preserves its manifest digest, so both pullspecs resolve to the *same* cache entry and share one `index.db`.

Cache entries written under the previous pullspec-derived naming scheme are orphaned by this change — they are not migrated in place. On the normal path IIB never falls back to extracting `index.db` from the image: if the digest-keyed artifact is missing, the request fails with a "no index.db found for the image, onboard the image to build" error, and the image must be onboarded (which populates the artifact) before it can be built. Orphaned entries are cleaned up by the existing cache-pruning process rather than any code path in this workflow.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[high] scope-creep

The content-key migration orphans every existing ORAS-cached index.db artifact in production. The README explicitly documents this: cached entries under the previous naming scheme are orphaned, and affected images fail with 'no index.db found' until re-onboarded. Every onboarded image will begin failing immediately after deployment with no fallback path or migration mechanism.

Suggested fix: Add an explicit deployment checklist item, or introduce a transient fallback that checks the digest-keyed artifact and, if absent, falls back to the old pullspec-keyed artifact for one release cycle.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

this is intentional. There are no production index.db atm


set_request_state(request_id, 'in_progress', 'Pushing updated index database')
conf = get_worker_config()
output_tag = f'idb-{_get_index_digest(output_image)}'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] API-contract-violation

push_index_db_artifact hardcodes the artifact tag format as f'idb-{_get_index_digest(output_image)}' instead of using the configurable iib_index_db_artifact_tag_template from the worker config. The lookup path in _get_artifact_combined_tag (oras_utils.py:96) correctly uses the config template. If the template is overridden, pushed tags will not match lookup tags, causing cache misses or lookup failures.

Suggested fix: Replace the hardcoded tag with the config template: output_tag = conf['iib_index_db_artifact_tag_template'].format(digest=_get_index_digest(output_image)).

catalog_path = local_git_repo_path / 'configs'
if catalog_path.exists():
shutil.rmtree(catalog_path)
shutil.copytree(extracted_configs, catalog_path)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] path-traversal

On the divergent-tag path, shutil.copytree(extracted_configs, catalog_path) uses the default symlinks=False, which follows symlinks on the host filesystem. The upstream extract_files_from_image_non_privileged (line 121) explicitly uses symlinks=True. A malicious from_index image embedding an absolute symlink in its /configs directory could exfiltrate host-local files into the git-committed catalog_path.

Suggested fix: Pass symlinks=True to the shutil.copytree call at line 690, or add a post-extraction pass that detects and removes symlinks targeting paths outside the temp directory.

Comment thread iib/workers/config.py
os.path.expanduser('~'), '.docker', 'oras', 'config.json'
)
iib_index_db_artifact_tag_template: str = '{image_name}-{tag}'
iib_index_db_artifact_tag_template: str = 'idb-{digest}'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] breaking-config-interface

The format variable names in iib_index_db_artifact_tag_template changed from {image_name} and {tag} to {digest}. Any deployment overriding this setting in a site-level settings.py will get an unhandled KeyError at runtime when _get_artifact_combined_tag calls .format(digest=...) on a template referencing {image_name} and {tag}.

Suggested fix: Validate the config key's format string at startup (check that {digest} is present, warn if {image_name}/{tag} are found), or document that the template format is not operator-configurable.

# returning empty output that would be misread as "branch absent". The
# exc_msg deliberately references repo_url, not the token-injected URL.
remote_branch_status = run_cmd(
["git", "ls-remote", "--heads", ls_remote_url, branch],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] secret-exposure

The new remote_branch_exists function injects credentials into the git URL. The error-path log.error in run_cmd (utils.py line 923) logs the raw command without sanitization. Pre-existing pattern, but adds another affected call site.

Suggested fix: Fix run_cmd to use _sanitize_cmd_log in the log.error call at utils.py line 923.

)


def prepare_git_repository_for_build(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] naming-abstraction

prepare_git_repository_for_build is retained alongside the new prepare_build_sources for request types where divergent-tag detection does not apply (merge, create_empty_index), but lacks any comment explaining why it was not migrated.

Suggested fix: Add a docstring note stating it is intentionally retained for request types where divergent-tag detection does not apply.

:param str branch: The branch name to check.
:param str token_name: Optional name of the Git repository token.
:param str token: Optional value of the Git repository token.
:rtype: bool

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] documentation-comment-format

remote_branch_exists uses :rtype: bool with no :return: description, diverging from the file's established :return: convention.

Suggested fix: Replace ':rtype: bool' with ':return: True if the branch exists on the remote, False otherwise.'

string defined in the worker configuration and replacing placeholders with the
provided image name and tag.
:param str pullspec: The full index image pullspec.
:return: The 64-char hex digest without the ``sha256:`` prefix.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] documentation-comment-format

Three new functions use :return: + :rtype: on separate lines, while pre-existing functions use :returns Type: description inline. Introduces a mixed docstring style.

Suggested fix: Adopt one consistent style across all functions in the file.

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

Labels

risk/moderate PR risk: moderate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants