Skip to content

chore(OMN-14216): install import-linter + encode intra-core dependency-layering oracle (CI + pre-commit) - #1407

Merged
jonahgabriel merged 2 commits into
devfrom
jonah/omn-14216-import-linter-oracle
Jul 9, 2026
Merged

jonahgabriel merged 2 commits into
devfrom
jonah/omn-14216-import-linter-oracle

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Jul 9, 2026 •

Copy link
Copy Markdown
Contributor

What & why

omnibase_core documents an intended internal dependency layering (ADR-001, ADR-005, docs/architecture/DEPENDENCY_INVERSION.md) but nothing mechanically enforces the direction of imports between its sub-packages. Existing guards (scripts/check_transport_imports.py, scripts/validation/validate-no-infra-imports.py, tests/unit/test_no_circular_imports.py) cover cross-repo/transport boundaries and circularity — not intra-core sub-package layering.

This PR is ordinary architecture hygiene: it installs import-linter and encodes the intra-core directional layering as a deterministic, CI- and pre-commit-enforced contract. New illegal back-edges fail the gate; the current known back-edges are frozen as an explicit, annotated allowlist to burn down under OMN-3210 (Platform Tech Debt Reduction).

The contract (.importlinter)

Doctrine layering (high → low): nodes → (services | validation) → models → (constants | enums | errors | types), with protocols as the low seam layer. Encoded as four forbidden contracts (direct-import mode):

  • core-foundation-no-upward — foundation primitives must not import models/services/validation/nodes
  • core-models-no-upward — models must not import services/validation/nodes
  • core-midtier-no-top — services/validation must not import nodes
  • core-protocols-no-upward — protocols (seam) must not import models/services/validation/nodes

Why forbidden (direct) and not a transitive layers contract

Re-derived independently with grimp (import-linter's own engine, 4078 modules): core's graph is densely bridged through hub modules (utils, mixins, protocols), so a transitive layers contract can't be expressed today without an unmaintainable bridge allowlist. The contracts therefore enforce direct imports (allow_indirect_imports = True). Tightening to a transitive contract is a follow-up gated on slimming those hub modules. protocols ↔ models is a real cycle: the protocols → models direction (~65 protocol I/O type refs) is frozen debt; the reverse models → protocols is left permitted.

Frozen back-edges (allowlist, burn down under OMN-3210)

The doctrine's clean layer story does not fully hold. Direct back-edges frozen (8 classes / ~124 edges), each annotated in .importlinter:

back-edge ~edges note
protocols → models 65 protocol I/O type references
types → models 23 TypedDict shape references
models → validation 18
errors → models 11
models → nodes 4
constants → models 1
types → validation 1
validation → nodes 1

unmatched_ignore_imports_alerting is left at its ERROR default, so when a class is fully removed its stale ignore line forces its own deletion (self-cleaning ratchet).

Enforcement (same PR, Rule #5)

  • CI: new lint-imports job (Import Layering Oracle) wired into the Phase-1 quality-gate (needs + gate condition + summary row).
  • pre-commit: lint-imports hook (pre-commit stage; grimp caches the graph so re-runs are sub-second).
  • .import_linter_cache/ added to .gitignore.

Follow-up recommendation for the operator: add the lint-imports context to dev branch-protection required_status_checks once it has run green for a few PRs.

dod_evidence

Local verification (worktree off origin/dev):

$ uv run lint-imports
Analyzed 4078 files, 10327 dependencies.
Foundation primitives must not import higher layers (direct) KEPT
Models must not import behavioral/orchestration layers (direct) KEPT
Services and validation must not import nodes (direct) KEPT
Protocols (seam) must not import domain/behavioral layers (direct) KEPT
Contracts: 4 kept, 0 broken.
  • Negative test: removing any one frozen ignore makes the corresponding contract BROKEN and lint-imports exits 1 (gate genuinely gates).
  • uv run ruff format --check src/ tests/ → clean; uv run ruff check src/ tests/ → All checks passed
  • uv run mypy src/omnibase_core/ → Success: no issues found in 4079 source files
  • uv run pytest tests/unit/test_no_circular_imports.py → 20 passed
  • uv run pre-commit run --files <changed files> → all Passed/Skipped (yamlfmt, ruff, import-linter, gitignore-baseline included)
  • uv lock --check → consistent

Diff is config/CI/deps only — no src/ or tests/ Python changed. Full suite runs in CI on this PR.

Closes OMN-14216
Refs OMN-3210

Evidence-Source: OCC#3768
Evidence-Ticket: OMN-14216

…y-layering oracle

Install import-linter (dev dep) and add `.importlinter` encoding the DIRECTION
of imports between omnibase_core's architectural sub-packages as `forbidden`
contracts (direct-import mode). This is the enforcement oracle later decoupling
work verifies against: new illegal back-edges fail `lint-imports`; current known
back-edges are frozen as a minimal, annotated allowlist to burn down under
OMN-3210.

Enforcement wired in the same PR (Rule #5): a `lint-imports` CI job in the
Phase-1 quality gate + a `lint-imports` pre-commit hook.

A transitive `layers` contract is deferred (documented in .importlinter): core's
graph is densely bridged through hub modules (utils/mixins/protocols), so a
direct-import contract is the honest, green-today formulation.

Closes OMN-14216
Refs OMN-3210
@coderabbitai

coderabbitai Bot commented Jul 9, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 058a41f6-411a-4aa6-9f5d-4353af249ca3

📥 Commits

Reviewing files that changed from the base of the PR and between d823740 and 15e850b.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • .github/workflows/ci.yml
  • .gitignore
  • .importlinter
  • .pre-commit-config.yaml
  • pyproject.toml
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jonah/omn-14216-import-linter-oracle

Comment @coderabbitai help to get the list of available commands.

…>models)

Adds a 4th forbidden contract: protocols (the seam layer) must not import the
domain/behavioral layers (models/services/validation/nodes). protocols sits low
(everything depends on protocol interfaces), so this strengthens the oracle by
catching any NEW protocols->services/validation/nodes edge (currently 0). The
existing protocols->models direction (~65 protocol I/O type references) is frozen
as burn-down debt under OMN-3210; the reverse models->protocols is left permitted.

Per graph-pass steer from the lead. lint-imports: 4 kept, 0 broken.

Refs OMN-14216 OMN-3210
@jonahgabriel

Copy link
Copy Markdown
Contributor Author

Independent verification (leaf-0 verifier, not the implementing agent)

Reproduced against both the original 3-contract head bb19d723 and the current 4-contract head 15e850bc (which adds core-protocols-no-upward per the lead's graph-pass steer). All runs done in an isolated worktree with the canonical-clone PYTHONPATH cleared so grimp analyzed the PR-branch source.

lint-imports GREEN (15e850b): Analyzed 4078 files, 10327 dependencies. Contracts: 4 kept, 0 broken.

The gate genuinely gates (negative tests, each reverted):

  • foundation enums->nodes / enums->services → BROKEN (exit 1)
  • models->services and models->nodes (bare pkg) → BROKEN
  • services->nodes → BROKEN
  • protocols->services and protocols->nodes → BROKEN
  • an edge inside an already-frozen family (e.g. models->nodes submodule, protocols->models submodule) stays GREEN — the frozen wildcards are family-broad, so only NEW-family back-edges + bare-package imports are caught. This is the documented OMN-3210 freeze tradeoff, not a defect.

Ignore-list honesty (no padding): removing any one of the 8 frozen ignore families (types→models, errors→models, constants→models, types→validation, models→validation, models→nodes, validation→nodes, protocols→models) turns exactly one contract BROKEN — each ignore is load-bearing. A bogus unmatched ignore errors (unmatched_ignore_imports_alerting=ERROR ratchet active).

Also independently confirmed: mypy src/omnibase_core/ → 4079 files, 0 issues; test_no_circular_imports → 20 passed; lint-imports CI job wired into quality-gate needs + gate condition + summary; pre-commit hook present; changed set is config-only (.importlinter, .github/workflows/ci.yml, .pre-commit-config.yaml, .gitignore, pyproject.toml, uv.lock).

Evidence-Source OCC#3768 (merged) carries the independent-verify receipt for bb19d723; this comment extends that verification to the current head 15e850bc (4 contracts).

@jonahgabriel
jonahgabriel marked this pull request as ready for review July 9, 2026 21:47
@jonahgabriel
jonahgabriel merged commit 3807a3c into dev Jul 9, 2026
198 of 246 checks passed
@jonahgabriel
jonahgabriel deleted the jonah/omn-14216-import-linter-oracle branch July 9, 2026 21:55
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.

1 participant