Every interface that crosses a package boundary in the framework must live in
oridecon-contracts — a zero-dependency package that only ever defines
types, protocols, and exceptions. This guide states the rules for deciding
where a new contract goes, when a local protocol is allowed, and how a
consumer upstreams a contract.
The golden rule: a type, protocol, or exception shared by two or more
packages belongs in oridecon-contracts, organized by domain directory —
never by package name.
1. The Rules
Section titled “1. The Rules”R1 — The golden rule
Section titled “R1 — The golden rule”A type, protocol, or exception referenced by two or more packages must
live in oridecon-contracts, under the domain directory that describes
what it is, not who uses it.
| Shared by | Canonical home |
|---|---|
| Vector-store + AI packages | oridecon.contracts.ai.vector (e.g. DocumentProtocol, ChunkerProtocol) |
| Web + admin packages | oridecon.contracts.admin (e.g. AdminError, BaseAdminContributor) |
| Any package + CLI tooling | oridecon.contracts.cli (e.g. GenerationResult, parse_fields) |
| RAG pipeline packages | oridecon.contracts.ai.rag, oridecon.contracts.ai.vector |
Rule of thumb: if the same import statement appears in two or more
packages, the symbol is a contract. One package importing a symbol from
another package’s internals is a boundary violation.
R2 — Experimental contracts still land in the stable contracts package
Section titled “R2 — Experimental contracts still land in the stable contracts package”There is no separate experimental contracts package and none may be
created. AI, multimedia, CLI, admin, and UI live under experimental/ in
the same public repo (dbtinoy-/oridecon,
branch dev). Their cross-package contracts still live in
oridecon-contracts under clearly experimental domain directories:
| Domain directory | Serves |
|---|---|
oridecon.contracts.ai | the AI platform (oridecon-ai + oridecon-ai-*) |
oridecon.contracts.multimedia | oridecon-multimedia + oridecon-multimedia-* |
oridecon.contracts.admin | oridecon-admin and the packages that consume it |
oridecon.contracts.cli, oridecon.contracts.ui | oridecon-cli, oridecon-ui |
A new experimental domain directory requires a short proposal stating: which packages share it, which stable packages consume it (if any), and which core domains it imports.
R3 — Stability is marked by consumption, not by directory
Section titled “R3 — Stability is marked by consumption, not by directory”A contract consumed by a stable package is de facto stable, regardless of the domain directory it lives in — treat it as frozen (semver minor+ only).
Experimental domain directories may evolve (breaking changes are permitted
within reason) but may import only core contracts domains — e.g.
core.result, core.di, infra.*, exceptions.domain,
data.vector.exceptions, observability.ai. The dependency direction is
inward only: oridecon.contracts.ai → core domains is sanctioned; core
domains → oridecon.contracts.ai is a violation.
R4 — Local protocols are seams, not contracts
Section titled “R4 — Local protocols are seams, not contracts”A package may keep its own protocols.py for internal implementation
seams (e.g. a plugin used only inside the package). It must be consumed only
within that package. A cross-package import of a local protocol module is a
violation — the protocol must be promoted into the matching contracts domain
per R1/R2.
This rule is enforced mechanically: .importlinter forbids cross-package
imports of the known local protocol modules (see the local-protocols-scoped
contract). Run the check with:
uv run python tools/lint_imports.pyR5 — One canonical definition
Section titled “R5 — One canonical definition”No duplicate protocol, type, or exception definitions. If a symbol already
exists in oridecon-contracts, reference it — never copy it into a package.
A local copy with a different signature is drift, not isolation.
R6 — Consumer contracts stay in the consumer project
Section titled “R6 — Consumer contracts stay in the consumer project”A contract used only by a consumer’s application stays in that application. It never becomes a new framework package.
To upstream a consumer contract that became generally useful:
- Submit a proposal (this plan’s format: context, evidence of ≥2 packages sharing it, target domain directory).
- Get it reviewed against R1–R3.
- Move it into the canonical contracts domain directory.
- Change the consumer to depend on
oridecon-contracts.
2. Decision Tree
Section titled “2. Decision Tree”New type / protocol / exception needed|+-- Used by >= 2 framework packages?| || +-- YES --> oridecon-contracts, by domain directory| | || | +-- experimental domain (ai, multimedia) --> R2 (same package, experimental dir)| | +-- stable domain/consumed by stable pkg --> R1 + freeze (R3)| || +-- NO --> imported from another package today? --> promote to contracts (R4 violation)| | +-- single-package internal seam --> local protocols.py (R4)|+-- Consumer application code only --> stays in the consumer project (R6)3. Where packages live
Section titled “3. Where packages live”All 54 packages are in dbtinoy-/oridecon (dev). There is no second GitHub repository.
| Area | Path in the monorepo |
|---|---|
| Foundation | core/oridecon, core/oridecon-contracts |
| Web, data, security, events, infra, testing | packages/oridecon-* |
| AI platform (17) | experimental/ai/ |
| Multimedia (8) | experimental/multimedia/ |
| CLI, admin, UI | experimental/apps/ |
“Experimental” means the surface still moves, not a different license or a different repo. oridecon-contracts is the single contract package for all of them.
4. Checklist for Adding a Contract
Section titled “4. Checklist for Adding a Contract”- Who consumes it? If ≥2 packages →
oridecon-contracts. - Which domain directory describes it? (never a package name)
- Does it already exist in contracts? If yes, reference it (R5).
- If experimental: is the domain dir sanctioned (ai/multimedia/admin/ui/cli) or does it need a proposal (R2)?
- Does it import only core domains (R3)?
- If it stays local: is it a single-package seam, and does it pass
python tools/lint_imports.py(R4)? - Consumer-only? Leave it in the consumer project (R6).