Skip to content

Core and connector follow-ups from the codebase review (#197) - #206

Merged
icebergai-review-bot[bot] merged 1 commit into
mainfrom
claude/codebase-review-cleanup-ovlli4
Aug 20, 2026
Merged

Core and connector follow-ups from the codebase review (#197)#206
icebergai-review-bot[bot] merged 1 commit into
mainfrom
claude/codebase-review-cleanup-ovlli4

Conversation

@richardmhope

Copy link
Copy Markdown
Contributor

The core and connectors sections of #197, after the API section in #205. The web/deploy section follows; #197 stays open until it lands.

1. The shared pytest plugin broke minimal environments

iceberg-core publishes iceberg_core.testing under pytest11, so pytest imports it in every environment where the package is installed — including one carrying the engine's dependency shape: plain iceberg-core, no db extra, no SQLAlchemy (ADR 0002). The import failed at collection, before a single test ran, in a package that deliberately does not depend on what it needed.

The ORM imports are guarded and the database fixtures pytest.skip with a sentence naming what to install. They stay registered rather than vanishing — a fixture that disappeared would be an obscure fixture 'session' not found, and a test that wanted a session is now reported skipped rather than passing on nothing.

Tested in a subprocess with sys.modules[name] = None, because the workspace this suite runs in has the extra installed and can never reproduce the condition in-process.

2. Jira spliced stored state into quoted JQL literals

Three values reach f'{field} >= "{start}"' with only an isinstance(str) check: a discovery window's bounds (from the lease), a resume point (from the checkpoint), and a watermark (from the cursor). All three round-trip through the API's database after this connector writes them, and a " in one closes the literal and changes the query rather than failing it.

Each is now checked against the single shape _minute writes (YYYY-MM-DD HH:MM). An unusable value is dropped, not fatal: the query widens, so the cost is re-reading issues that dedupe on their fingerprint — the same trade the resume logic already states a few lines down ("re-reading costs a duplicate, skipping costs the secret"). A checkpoint carrying one restarts the spec, exactly as an unknown checkpoint version does.

3. Support code copied into each connector is now shared

_LazySandbox was byte-identical in Confluence and Jira. The extraction-outcome mapping was in all three — and had already drifted:

Confluence / Jira file share
REJECTED_BOMB parse_error size_limit

Same file, same refusal, two manifest reasons depending on which connector met it. Both move to extraction.py, beside the outcomes they translate and the is_incomplete property they must stay in lockstep with.

Resolved to size_limit, which is the reading the enum's own names point at: REJECTED_* is a decision this code made about how big something would become, FAILED_* is something that broke. An operator sent to look for a malformed file by a parse_error would find a perfectly well-formed one. No test pinned either behaviour, which is how it drifted — there is one now, plus a check that every non-EXTRACTED outcome has an entry so a new member cannot fall into some connector's default.

4. The enum CHECK-constraint question, decided

No constraint, and the reasoning is now in the docstring rather than the question being left open:

  • A CHECK reintroduces exactly the cost native_enum=False was chosen to avoid — adding a value becomes DROP/ADD CONSTRAINT, a table scan under an ACCESS EXCLUSIVE lock on Postgres.
  • It defends against a writer this architecture says does not exist. Only the API writes, so "out of band" means someone holding the database credentials, who can drop the constraint as easily as violate it. It stops a typo in a manual UPDATE, not an adversary.
  • The value could never be believed anyway. Reading an unknown one raises LookupError naming the column's enum and its legal values — verified, not assumed. That property is what the trade rests on, so test_enum_columns.py pins it; if SQLAlchemy ever starts coercing instead of raising, the decision should be revisited and the test is what will say so.

Validation

Every behavioural fix has a test confirmed to fail against the code before it (stashed and re-run). New: test_pytest_plugin.py, test_enum_columns.py, test_extraction_reasons.py, plus JQL tests in test_jira_connector.py.

make check green: ruff, mypy, docs check, 2007 passed / 2 skipped.


Generated by Claude Code

**The shared pytest plugin broke minimal environments.** `iceberg-core`
publishes `iceberg_core.testing` under `pytest11`, so pytest imports it in every
environment where the package is installed — including one carrying the engine's
dependency shape, which is plain `iceberg-core` with no `db` extra and therefore
no SQLAlchemy at all (ADR 0002). The import failed at collection, before any
test ran, in a package that deliberately does not depend on what it needed. The
ORM imports are guarded and the database fixtures skip with a sentence naming
what to install; the fixtures stay registered rather than vanishing, so a test
that wanted one is reported skipped rather than passing on nothing.

**Jira spliced instants from stored state into quoted JQL literals** with only
an isinstance check. A discovery window's bounds arrive on the lease, a resume
point on the checkpoint, a watermark on the cursor — all three round-trip
through the API's database after this connector writes them, and a `"` in one
would close the literal and change the query rather than fail it. Each is now
checked against the single shape `_minute` writes. An unusable value is dropped
rather than fatal: the query widens, so the cost is re-reading issues that
dedupe on their fingerprint, which is the trade the resume logic already makes.

**Support code copied into each connector is now shared.** `_LazySandbox` was
byte-identical in two of them; the extraction-outcome mapping was in all three,
and had already drifted — a compression bomb was a `size_limit` on a file share
and a `parse_error` in Confluence and Jira, so a manifest reason depended on
which connector met the file. Both move to `extraction.py`, beside the outcomes
they translate. `REJECTED_BOMB` is a `size_limit` everywhere now, which is the
reading the enum's own names point at: `REJECTED_*` is a decision about how big
something would become, `FAILED_*` is something that broke.

**The enum CHECK-constraint question is decided rather than left open**: no
constraint. It would make adding a value a locking migration again — exactly
what `native_enum=False` was chosen to avoid — while defending only against a
writer holding the database credentials directly, who could drop it as easily as
violate it. What makes that safe is that an unknown value can never be
*believed*: reading one raises, naming the column's vocabulary. The reasoning is
in the docstring and the property it rests on is now a test.

Refs ADR 0002.

Claude-Session: https://claude.ai/code/session_012sohE85sRDt6t2w3936rGJ

Co-authored-by: Claude <noreply@anthropic.com>

@icebergai-review-bot icebergai-review-bot 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.

Verdict

APPROVE

Completed bounded review across 1 immutable scope(s). No actionable findings in the supplied immutable change.

Scope health

Convergence: healthy. Review mode: initial.
Recommended action: CONTINUE_INCREMENTAL.

  • No escalation signals.

Prior findings

Finding Status
No prior finding state

New findings

No new findings.

Fix-induced regressions

  • None evidenced.

Uncertainty

  • No material uncertainty recorded.

Validation

  • Reviewed the supplied diff; exact-head CI is reported as passed.

Residual risks

  • None identified.

@icebergai-review-bot
icebergai-review-bot Bot merged commit 69f4f86 into main Aug 20, 2026
6 checks passed
@icebergai-review-bot
icebergai-review-bot Bot deleted the claude/codebase-review-cleanup-ovlli4 branch August 20, 2026 02:00
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