Repository navigation
feat(contracts): add contract-driven DB repository schema and validat… #463
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
jonahgabriel
merged 9 commits into
main
from
jonah/omn-1782-core-contract-driven-db-repository-schema-validators
Feb 1, 2026
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
8597ce7
feat(contracts): add contract-driven DB repository schema and validat…
jonahgabriel 6f33002
refactor(validation): consolidate SQL utils and add escaped quote tes…
jonahgabriel e5bee64
fix(validation): exclude virtual environments from file location checks
jonahgabriel f185773
fix(validation): address code review feedback for DB repository valid…
jonahgabriel b49d01a
merge: resolve conflicts with main branch
jonahgabriel a7c442f
fix(validation): address all PR #463 review feedback (OMN-1782)
jonahgabriel 26437b4
fix(validation): address final PR #463 review feedback (OMN-1782)
jonahgabriel 9bb2e01
feat(contracts): add EnumDatabaseEngine for multi-engine support (OMN…
jonahgabriel f2a2947
fix(validation): support schema-qualified table names in structural v…
jonahgabriel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | | ||
| |------|-------------| | ||
| | `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. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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"] |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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