Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ name: CI
on:
workflow_call:
pull_request:
branches: [main]
branches: [main, feat/ir-first]
push:
branches: [main]
branches: [main, feat/ir-first]
workflow_dispatch:

concurrency:
Expand Down Expand Up @@ -126,6 +126,10 @@ jobs:
run: |
uv run maturin develop

- name: Run IR vector contract harness
run: |
uv run pytest -v tests/test_ir_vectors_contract.py

- name: Run pytest
run: |
uv run pytest -v --cov=src --cov-report=xml --cov-report=term
Expand Down Expand Up @@ -169,6 +173,10 @@ jobs:
run: |
uv run maturin develop

- name: Run IR vector contract harness
run: |
uv run pytest -v tests/test_ir_vectors_contract.py

- name: Run pytest
run: |
uv run pytest -v --cov=src --cov-report=xml --cov-report=term
Expand Down
40 changes: 34 additions & 6 deletions docs/plans/2026-06-19-001-ir-first-roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,21 @@ Definition of done addition:
- Last updated: `2026-06-19`
- Roadmap owner: `@syn54x`

## Branching and release policy

IR program integration branch:

- `feat/ir-first` is the staging branch for all roadmap work.
- Starting work on any phase issue requires creating a new branch from `feat/ir-first`.
- Phase PRs must target `feat/ir-first` (not `main`).
- Completed phase work merges back into `feat/ir-first`.

Promotion to release:

- `main` must not receive partial IR migration work.
- Merge `feat/ir-first` into `main` only when the IR program is complete and release-ready.
- Release notes and migration guide updates are required at promotion time.

## Traceability rule (roadmap <-> GitHub issues)

- Every roadmap deliverable and exit gate must reference one or more GitHub issues.
Expand Down Expand Up @@ -135,7 +150,7 @@ Definition of synchronized:

### Phase 0 - Contract and RFC freeze

Status: `Not started`
Status: `In progress`

Issue references:

Expand All @@ -146,14 +161,26 @@ Issue references:
- Define versioned IR contracts and non-negotiable invariants before implementation.

**Deliverables**
- [ ] RFC: `SchemaIR`, `QueryIR`, `CodecIR` structure and versioning strategy.
- [ ] Invariant spec doc covering parity, hydration ABI, null/bind correctness.
- [ ] Golden test vector format for schema/query/codec conformance fixtures.
- [x] RFC: `SchemaIR`, `QueryIR`, `CodecIR` structure and versioning strategy.
- [x] Invariant spec doc covering parity, hydration ABI, null/bind correctness.
- [x] Golden test vector format for schema/query/codec conformance fixtures.

**Exit gate**
- [ ] RFC approved and merged.
- [ ] Golden vectors committed and validated by CI harness skeleton.

**Evidence (branch `feat/ir-first`)**
- RFC draft: `docs/rfc/ir-contracts-v1.md`
- Invariant spec draft: `docs/solutions/patterns/ir-invariants.md`
- Golden vectors: `tests/fixtures/ir_vectors/README.md`, `tests/fixtures/ir_vectors/*.json`
- CI harness skeleton: `tests/test_ir_vectors_contract.py`
- CI wiring: `.github/workflows/ci.yml` (IR vector contract harness step in Python test jobs)
- Issue sync comments:
- [#71 comment](https://github.com/syn54x/ferro-orm/issues/71#issuecomment-4752226422)
- [#72 comment](https://github.com/syn54x/ferro-orm/issues/72#issuecomment-4752226080)
- [#73 comment](https://github.com/syn54x/ferro-orm/issues/73#issuecomment-4752226172)
- [#74 comment](https://github.com/syn54x/ferro-orm/issues/74#issuecomment-4752226261)

---

### Phase 1 - Build IR core and compiler
Expand Down Expand Up @@ -408,9 +435,10 @@ Use this roadmap as the source for issues and project fields.
Append updates as concise entries.

- `2026-06-19` - Roadmap initialized.
- `2026-06-19` - Branching policy set: phase work branches from `feat/ir-first` and merges back into `feat/ir-first` until final promotion to `main`.

## Immediate next actions

- [ ] Create GitHub issues for Phase 0 deliverables.
- [ ] Create `IR-P0` milestone and seed with Phase 0 issues.
- [x] Create GitHub issues for Phase 0 deliverables.
- [x] Create `IR-P0` milestone and seed with Phase 0 issues.
- [ ] Assign DRO for Phase 0 RFC.
6 changes: 3 additions & 3 deletions docs/plans/ir-first-migration-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ No user-facing runtime behavior changes expected.

| Issue | Change | Impact | User action | Notes |
| --- | --- | --- | --- | --- |
| [#72](https://github.com/syn54x/ferro-orm/issues/72) | IR contract RFC definition | none | none | design-only |
| [#73](https://github.com/syn54x/ferro-orm/issues/73) | Invariant specification | none | none | design-only |
| [#74](https://github.com/syn54x/ferro-orm/issues/74) | Golden vectors + CI harness skeleton | none | none | infra-only |
| [#72](https://github.com/syn54x/ferro-orm/issues/72) | IR contract RFC definition | none | none | design-only; artifact: `docs/rfc/ir-contracts-v1.md` |
| [#73](https://github.com/syn54x/ferro-orm/issues/73) | Invariant specification | none | none | design-only; artifact: `docs/solutions/patterns/ir-invariants.md` |
| [#74](https://github.com/syn54x/ferro-orm/issues/74) | Golden vectors + CI harness skeleton | none | none | infra-only; artifacts: `tests/fixtures/ir_vectors/`, `tests/test_ir_vectors_contract.py` |

### Phase 1

Expand Down
228 changes: 228 additions & 0 deletions docs/rfc/ir-contracts-v1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
---
title: "IR contracts v1 (SchemaIR, QueryIR, CodecIR)"
type: rfc
status: draft
date: 2026-06-19
phase: IR-P0
roadmap: docs/plans/2026-06-19-001-ir-first-roadmap.md
---

# IR contracts v1

## Purpose

Define the v1 canonical intermediate representation contracts for schema, query, and bind/fetch codec behavior before Phase 1 implementation.

This RFC is normative for wire shape, versioning, and validation behavior.

## Non-goals

- Implementing new runtime/compiler behavior (Phase 1+).
- Removing compatibility layers in existing JSON pipelines (later phases).

## Shared contract rules

### Envelope

All IR artifacts use a top-level envelope:

```json
{
"ir_kind": "schema|query|codec",
"ir_version": 1,
"payload": {}
}
```

Rules:

- `ir_kind` and `ir_version` are required.
- Unknown `ir_kind` is a hard error.
- Unsupported `ir_version` is a hard error.
- `payload` must be an object; missing or non-object payload is a hard error.

### Compatibility policy

- Minor additive evolution inside `ir_version: 1` may add optional payload fields.
- Existing required fields in v1 cannot be removed or retyped.
- New required fields require a major version bump.
- Readers must fail loudly on malformed required fields.
- Writers must emit deterministic field ordering where serialization APIs allow it.

## SchemaIR v1

`SchemaIR` represents canonical model schema semantics consumed by runtime DDL, migration planning, and Alembic adapters.

### SchemaIR payload shape

```json
{
"dialect_agnostic": true,
"models": [
{
"model_name": "Invoice",
"table_name": "invoice",
"columns": [
{
"name": "id",
"logical_type": "integer",
"db_type": "bigint",
"nullable": false,
"primary_key": true,
"autoincrement": true,
"unique": false,
"index": false,
"default": null,
"format": null
}
],
"foreign_keys": [
{
"column": "customer_id",
"to_table": "customer",
"to_column": "id",
"on_delete": "CASCADE",
"name": null
}
],
"indexes": [
{
"name": "idx_invoice_created_at",
"columns": ["created_at"],
"unique": false
}
],
"uniques": [
{
"name": "uq_invoice_number",
"columns": ["number"]
}
],
"checks": [
{
"name": "ck_invoice_total",
"expression": "total >= 0"
}
]
}
]
}
```

### SchemaIR requirements

- `table_name` must be canonical lower-case model table name.
- `db_type` must use canonical Ferro tokens (`text`, `varchar(N)`, `smallint`, `int`, `bigint`, `uuid`, `timestamp`, `timestamptz`, `date`, `time`).
- `nullable` is explicit and never inferred by the reader.
- Constraint/index names are required and must follow cross-emitter parity naming rules.
- Foreign key shadow columns (for `ForeignKey`) are represented as normal columns plus FK metadata.
- Any schema artifact emitted by one emitter must be representable without lossy translation.

## QueryIR v1

`QueryIR` represents typed query intent currently serialized through ad-hoc query JSON.

### QueryIR payload shape

```json
{
"model_name": "User",
"where": [
{
"node_kind": "compound",
"operator": "AND",
"left": {
"node_kind": "leaf",
"column": "active",
"operator": "==",
"value": {"kind": "bool", "value": true}
},
"right": {
"node_kind": "leaf",
"column": "email",
"operator": "LIKE",
"value": {"kind": "string", "value": "%@example.com"}
}
}
],
"order_by": [
{"column": "id", "direction": "asc"}
],
"limit": 100,
"offset": 0,
"m2m": null
}
```

### QueryIR requirements

- Node variants are explicit (`leaf` vs `compound`), never inferred from nullable fields.
- Operator domain is restricted to: `==`, `!=`, `<`, `<=`, `>`, `>=`, `IN`, `LIKE`, `AND`, `OR`.
- `where` is a list of root predicate trees combined by implicit AND semantics unless nested compound nodes define otherwise.
- `order_by.direction` is normalized to `asc|desc`.
- Value literals are typed nodes (`kind`, `value`) so codec selection does not depend on lossy JSON inference.
- Null comparisons for equality/inequality map to `IS NULL` / `IS NOT NULL` semantics in execution lowering.

## CodecIR v1

`CodecIR` centralizes type semantics for both bind and fetch paths.

### CodecIR payload shape

```json
{
"bind_rules": [
{
"logical_type": "uuid",
"db_type": "uuid",
"non_null_wire_kind": "uuid",
"null_wire_kind": "uuid_null"
},
{
"logical_type": "integer",
"db_type": "bigint",
"non_null_wire_kind": "i64",
"null_wire_kind": "i64_null"
}
],
"fetch_rules": [
{
"db_type": "uuid",
"wire_kind": "uuid",
"python_kind": "uuid.UUID"
}
],
"hydration_abi": {
"constructor_mode": "direct_dict",
"required_slots": [
"__pydantic_fields_set__",
"__pydantic_extra__",
"__pydantic_private__"
]
}
}
```

### CodecIR requirements

- Typed null kinds are first-class and must not degrade to untyped text null in schema-driven paths.
- Bind and fetch semantics are defined per logical/db type pair.
- Hydration ABI explicitly requires slot initialization for observational equivalence with Pydantic initialization semantics.
- Runtime lowering must fail loudly when a required codec rule is absent.

## Validation behavior

For all IR kinds in v1:

- Parse/shape validation errors are fatal and actionable.
- Unknown required enum values are fatal.
- Unknown optional fields may be ignored by readers in the same major version.
- CI conformance vectors are the executable source of truth for schema/query/codec payload acceptance.

## Phase 1 handoff

Phase 1 implementation must:

1. Introduce strongly typed Rust/Python representations matching this RFC.
2. Add compile/serialize tests that round-trip all golden vectors without shape drift.
3. Keep existing behavior unchanged unless explicitly called out by roadmap phase gates.
Loading
Loading