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
113 changes: 113 additions & 0 deletions examples/contracts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Example Contracts

This directory contains example contract files demonstrating the ONEX contract formats.

## DB Repository Contracts

`db_repository_example.yaml` demonstrates the v1 DB repository contract format:

- **Named parameters** (`:param`) - the only supported style in v1
- **Read operations** - SELECT queries with ORDER BY for determinism
- **Write operations** - INSERT, UPDATE, DELETE with WHERE clauses
- **Model aliasing** - short names mapping to fully qualified imports
- **Pagination** - LIMIT/OFFSET with required ORDER BY

### Validation Rules

DB repository contracts are validated by 5 validators:

1. **Structural** - Valid identifiers, single statements
2. **SQL Safety** - Mode/verb matching, no DDL, WHERE requirements
3. **Table Access** - Only declared tables allowed
4. **Deterministic** - ORDER BY for many=true and LIMIT/OFFSET
5. **Parameters** - Named params match declarations

### Usage

```python
from omnibase_core.runtime.runtime_file_registry import FileRegistry
from pathlib import Path

registry = FileRegistry()
contract = registry.load(Path("examples/contracts/db_repository_example.yaml"))
```

## Other Contract Examples

| File | Description |
|------|-------------|

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.

⚠️ Potential issue | 🟡 Minor

Fix markdownlint MD060 table spacing.

Suggested change
-|------|-------------|
+| ------ | ------------- |
@@
-|------|-------------|-------------|
+| ------ | ------------- | ------------- |
@@
-|-------|-------|-----|
+| ------- | ------- | ----- |

Also applies to: 89-89, 100-100

🧰 Tools
🪛 markdownlint-cli2 (0.20.0)

[warning] 38-38: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 38-38: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 38-38: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 38-38: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)

🤖 Prompt for AI Agents
In `@examples/contracts/README.md` at line 38, Replace the malformed Markdown
table separator "|------|-------------|" (and the similar occurrences at the
other noted lines) with properly spaced Markdown table separator rows that
include at least three dashes per column and a single space padding around
pipes, e.g. change to "| --- | --- |" (or match column widths like "| ----- |
------------- |") so the table conforms to MD060 spacing rules.

| `orchestrator_data_pipeline.yaml` | Orchestrator node contract for data pipelines |
| `reducer_metrics_aggregator.yaml` | Reducer node contract for metrics aggregation |
| `compute/` | Compute node contract examples |
| `effect/` | Effect node contract examples |
| `handlers/` | Handler contract examples |

## Contract Format Reference

### DB Repository Contract Structure

```yaml
db_repository:
name: <repository_name> # Unique identifier
engine: postgres | mysql | sqlite # Database engine
database_ref: <database_name> # Database connection reference
description: | # Human-readable description
Multi-line description here.

tables: # Allowed tables (enforced)
- table_name

# Note: Table matching is case-insensitive. For example, `tables: ["Users"]`
# will match `FROM users`, `FROM USERS`, or `FROM Users` in SQL. This follows
# PostgreSQL's default identifier case-folding behavior. Quoted identifiers
# like `FROM "Users"` are compared case-sensitively and must match the
# declared table name exactly.

models: # Model aliases
AliasName: module.path:ClassName

ops: # Named operations
operation_name:
mode: read | write # Operation type
description: | # Operation description
What this operation does.
sql: | # SQL with named params
SELECT ... WHERE col = :param
params: # Parameter definitions
param_name:
name: param_name
param_type: string | integer | number | boolean
required: true | false
default: <value> # Optional default
ge: <min> # Optional: >= constraint
le: <max> # Optional: <= constraint
min_length: <n> # Optional: string min length
max_length: <n> # Optional: string max length
description: | # Optional description
returns:
model_ref: AliasName # Return type (from models)
many: true | false # Single vs multiple rows
```

### Parameter Types

| Type | Description | Constraints |
|------|-------------|-------------|
| `string` | Text value | `min_length`, `max_length` |
| `integer` | Whole number | `ge`, `le` |
| `number` | Decimal number | `ge`, `le` |
| `boolean` | True/false | None |

### Validation Errors

Common validation errors and fixes:

| Error | Cause | Fix |
|-------|-------|-----|
| `Missing ORDER BY` | `many: true` without ORDER BY | Add ORDER BY clause |
| `Table not allowed` | Query uses undeclared table | Add table to `tables` list |
| `Mode mismatch` | SELECT in write mode | Change mode to `read` |
| `Parameter undefined` | `:param` not in params | Add parameter definition |
| `DDL not allowed` | CREATE/DROP/ALTER in SQL | Remove DDL statements |

> **Note**: Table matching is case-insensitive, so `Table not allowed` errors are not caused by case differences. `tables: ["Users"]` will match `users`, `USERS`, or `Users` in SQL queries. However, quoted identifiers like `FROM "Users"` are compared case-sensitively and must match the declared table name exactly.
184 changes: 184 additions & 0 deletions examples/contracts/db_repository_example.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
---
# Example DB Repository Contract
#
# This contract demonstrates the v1 DB repository contract format.
# It defines a repository for the learned_patterns table with:
# - Read operations (SELECT with named params, ORDER BY for determinism)
# - Write operations (INSERT, UPDATE with WHERE)
# - Named parameters (:param format)
# - Model reference aliasing

db_repository:
name: learned_patterns_repository
engine: postgres
database_ref: omninode_bridge
description: |
Repository for accessing learned patterns from the pattern store.
Used by the pattern learning pipeline for CRUD operations.

# Tables this contract is allowed to access
# The table_access validator enforces this list
tables:
- learned_patterns

# Model aliases -> fully qualified import paths
# Allows short names in operation returns
models:
PatternRow: omnibase_spi.models:ModelCompiledPattern
PatternSummary: omnibase_spi.models:ModelPatternSummary

# Named operations
ops:
# =========================================================================
# READ OPERATIONS
# =========================================================================

list_validated_patterns:
mode: read
description: |
Retrieve validated patterns ordered by quality score.
Returns multiple rows, requires ORDER BY for determinism.
sql: |
SELECT pattern_id, domain, compiled_snippet, quality_score, created_at
FROM learned_patterns
WHERE status = 'validated'
ORDER BY quality_score DESC, pattern_id ASC
LIMIT :limit
params:
limit:
name: limit
param_type: integer
required: false
default: 200
ge: 1
le: 5000
description: Maximum number of patterns to return
returns:
model_ref: PatternRow
many: true

get_pattern_by_id:
mode: read
description: Retrieve a single pattern by its ID
sql: |
SELECT pattern_id, domain, compiled_snippet, quality_score, status, created_at
FROM learned_patterns
WHERE pattern_id = :pattern_id
params:
pattern_id:
name: pattern_id
param_type: string
required: true
description: Unique pattern identifier
returns:
model_ref: PatternRow
many: false

list_patterns_by_domain:
mode: read
description: |
Retrieve patterns filtered by domain with pagination.
Demonstrates LIMIT + OFFSET with required ORDER BY.
sql: |
SELECT pattern_id, domain, quality_score
FROM learned_patterns
WHERE domain = :domain
ORDER BY created_at DESC, pattern_id ASC
LIMIT :limit
OFFSET :offset
params:
domain:
name: domain
param_type: string
required: true
min_length: 1
max_length: 100
limit:
name: limit
param_type: integer
required: false
default: 50
ge: 1
le: 200
offset:
name: offset
param_type: integer
required: false
default: 0
ge: 0
returns:
model_ref: PatternSummary
many: true

# =========================================================================
# WRITE OPERATIONS
# =========================================================================

insert_pattern:
mode: write
description: Insert a new pattern into the store
sql: |
INSERT INTO learned_patterns (pattern_id, domain, compiled_snippet, quality_score, status, created_at)
VALUES (:pattern_id, :domain, :snippet, :quality_score, :status, NOW())
params:
pattern_id:
name: pattern_id
param_type: string
required: true
domain:
name: domain
param_type: string
required: true
snippet:
name: snippet
param_type: string
required: true
quality_score:
name: quality_score
param_type: number
required: true
ge: 0.0
le: 1.0
status:
name: status
param_type: string
required: false
default: "pending"
returns:
model_ref: PatternRow
many: false

update_pattern_status:
mode: write
description: Update the status of an existing pattern
sql: |
UPDATE learned_patterns
SET status = :new_status
WHERE pattern_id = :pattern_id
params:
pattern_id:
name: pattern_id
param_type: string
required: true
new_status:
name: new_status
param_type: string
required: true
returns:
model_ref: PatternRow
many: false

delete_pattern:
mode: write
description: Delete a pattern by ID (has WHERE clause, so no opt-in needed)
sql: |
DELETE FROM learned_patterns
WHERE pattern_id = :pattern_id
params:
pattern_id:
name: pattern_id
param_type: string
required: true
returns:
model_ref: PatternRow
many: false
13 changes: 11 additions & 2 deletions scripts/validation/validate-file-locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,19 @@ def validate(self) -> bool:
"""
# Find all Python files
for py_file in self.repo_path.rglob("*.py"):
# Skip __pycache__, archived, tests, .venv, site-packages
# Skip __pycache__, archived, tests, virtual environments
if any(
skip in str(py_file)
for skip in ["__pycache__", "archived", "archive", "tests", ".venv", "site-packages"]
for skip in [
"__pycache__",
"archived",
"archive",
"tests",
".venv",
"venv",
".tox",
"site-packages",
]
):
continue

Expand Down
4 changes: 4 additions & 0 deletions src/omnibase_core/enums/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@
# Security-related enums
from .enum_data_classification import EnumDataClassification

# Database engine enum (OMN-1782)
from .enum_database_engine import EnumDatabaseEngine

# Decision type enums (OMN-1235)
from .enum_decision_type import EnumDecisionType

Expand Down Expand Up @@ -502,6 +505,7 @@
# Infrastructure domain
"EnumAuthType",
"EnumBackoffStrategy",
"EnumDatabaseEngine",
"EnumNotificationMethod",
# Binding function domain (Operation Bindings DSL - OMN-1410)
"EnumBindingFunction",
Expand Down
50 changes: 50 additions & 0 deletions src/omnibase_core/enums/enum_database_engine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Database engine type enum for repository contracts.

Defines supported database engines for contract-driven data access.
"""

from __future__ import annotations

from enum import Enum, unique

from omnibase_core.utils.util_str_enum_base import StrValueHelper


@unique
class EnumDatabaseEngine(StrValueHelper, str, Enum):
"""Supported database engine types for repository contracts.

Used by ModelDbRepositoryContract to specify the target database.
Validators may apply engine-specific SQL syntax rules.
"""

POSTGRES = "postgres"
MYSQL = "mysql"
SQLITE = "sqlite"

@classmethod
def supports_schemas(cls, engine: EnumDatabaseEngine) -> bool:
"""Check if the engine supports schema-qualified table names."""
return engine in {cls.POSTGRES, cls.MYSQL}

@classmethod
def supports_returning(cls, engine: EnumDatabaseEngine) -> bool:
"""Check if the engine supports RETURNING clause on INSERT/UPDATE/DELETE."""
return engine in {cls.POSTGRES, cls.SQLITE}

@classmethod
def get_param_style(cls, engine: EnumDatabaseEngine) -> str:
"""Get the native parameter placeholder style for each engine.

Returns:
The parameter style: 'named' (:param), 'qmark' (?), or 'format' (%s).
"""
style_map = {
cls.POSTGRES: "named", # :param or $N (we use :param)
cls.MYSQL: "format", # %s (but we normalize to :param)
cls.SQLITE: "qmark", # ? (but we normalize to :param)
}
return style_map.get(engine, "named")


__all__ = ["EnumDatabaseEngine"]
Loading