agentic-data-contracts 0.9.1__tar.gz → 0.9.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/CHANGELOG.md +10 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/PKG-INFO +86 -65
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/README.md +74 -53
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/docs/architecture.md +2 -2
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/pyproject.toml +11 -9
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/core/session.py +13 -1
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_core/test_session.py +59 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/uv.lock +30 -30
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/.github/dependabot.yml +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/.github/workflows/ci.yml +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/.gitignore +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/.pre-commit-config.yaml +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/.python-version +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/CLAUDE.md +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/LICENSE +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/examples/revenue_agent/agent.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/examples/revenue_agent/contract.yml +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/examples/revenue_agent/semantic.yml +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/examples/revenue_agent/setup_db.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/__init__.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/adapters/__init__.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/adapters/_normalizer.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/adapters/base.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/adapters/duckdb.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/bridge/__init__.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/bridge/compiler.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/core/__init__.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/core/contract.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/core/prompt.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/core/schema.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/py.typed +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/semantic/__init__.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/semantic/base.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/semantic/cube.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/semantic/dbt.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/semantic/yaml_source.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/tools/__init__.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/tools/factory.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/tools/middleware.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/tools/sdk.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/validation/__init__.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/validation/checkers.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/validation/explain.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/validation/validator.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/__init__.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/conftest.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/fixtures/minimal_contract.yml +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/fixtures/relationships_checker.yml +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/fixtures/sample_cube_schema.yml +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/fixtures/sample_dbt_manifest.json +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/fixtures/semantic_source.yml +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/fixtures/valid_contract.yml +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_adapters/__init__.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_adapters/test_duckdb.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_bridge/__init__.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_bridge/test_compiler.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_core/__init__.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_core/test_contract.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_core/test_domain_model.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_core/test_load_semantic_source.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_core/test_prompt_renderers.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_core/test_scalability.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_core/test_schema.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_core/test_sdk_config.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_core/test_system_prompt_metrics.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_core/test_wildcard_tables.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_public_api.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_semantic/__init__.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_semantic/test_cube.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_semantic/test_dbt.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_semantic/test_relationships.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_semantic/test_search.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_semantic/test_yaml_source.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_tools/__init__.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_tools/test_auto_load.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_tools/test_factory.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_tools/test_middleware.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_tools/test_pagination.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_tools/test_relationship_tools.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_tools/test_sdk.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_tools/test_semantic_tools.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_tools/test_wildcard_tools.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_validation/__init__.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_validation/test_checkers.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_validation/test_explain.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_validation/test_relationship_checker.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_validation/test_result_checks.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_validation/test_sql_normalizer.py +0 -0
- {agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_validation/test_validator.py +0 -0
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.9.2] - 2026-04-15
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **Lazy session timer**: `ContractSession` no longer starts its wall-clock timer at construction. The timer now starts on the first `check_limits()` call, so idle time before the user's first interaction does not count against `temporal.max_duration_seconds`. This fixes premature "session expired" errors in long-lived agent setups (Chainlit, Webex bots) where the session object is created well before the first user message. (#16)
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **`ContractSession.reset_timer()`**: New method that resets the duration timer so it restarts on the next `check_limits()` call. Useful for frameworks with their own idle-timeout mechanisms that want to restart the clock on user activity.
|
|
14
|
+
|
|
5
15
|
## [0.9.1] - 2026-04-13
|
|
6
16
|
|
|
7
17
|
### Added
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentic-data-contracts
|
|
3
|
-
Version: 0.9.
|
|
4
|
-
Summary: YAML-first data
|
|
3
|
+
Version: 0.9.2
|
|
4
|
+
Summary: YAML-first, domain-driven data governance for AI agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/flyersworder/agentic-data-contracts
|
|
6
6
|
Project-URL: Repository, https://github.com/flyersworder/agentic-data-contracts
|
|
7
7
|
Project-URL: Issues, https://github.com/flyersworder/agentic-data-contracts/issues
|
|
@@ -9,7 +9,7 @@ Project-URL: Documentation, https://github.com/flyersworder/agentic-data-contrac
|
|
|
9
9
|
Author-email: Qing <qingye779@gmail.com>
|
|
10
10
|
License: MIT
|
|
11
11
|
License-File: LICENSE
|
|
12
|
-
Keywords: ai-agents,analytics,claude,data-contracts,data-governance,dbt,llm,sql-validation
|
|
12
|
+
Keywords: ai-agents,analytics,claude,data-contracts,data-governance,dbt,domain-driven,llm,semantic-layer,sql-validation
|
|
13
13
|
Classifier: Development Status :: 4 - Beta
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
15
|
Classifier: Intended Audience :: Science/Research
|
|
@@ -21,16 +21,16 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
|
21
21
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
22
|
Requires-Python: >=3.12
|
|
23
23
|
Requires-Dist: pydantic>=2.0
|
|
24
|
-
Requires-Dist: pyyaml>=6.0
|
|
24
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
25
25
|
Requires-Dist: sqlglot>=23.0
|
|
26
26
|
Requires-Dist: thefuzz>=0.22.1
|
|
27
27
|
Provides-Extra: agent-contracts
|
|
28
|
-
Requires-Dist: ai-agent-contracts>=0.
|
|
28
|
+
Requires-Dist: ai-agent-contracts>=0.3.0; extra == 'agent-contracts'
|
|
29
29
|
Provides-Extra: agent-sdk
|
|
30
|
-
Requires-Dist: claude-agent-sdk>=0.1.
|
|
30
|
+
Requires-Dist: claude-agent-sdk>=0.1.58; extra == 'agent-sdk'
|
|
31
31
|
Provides-Extra: all
|
|
32
|
-
Requires-Dist: ai-agent-contracts>=0.
|
|
33
|
-
Requires-Dist: claude-agent-sdk>=0.1.
|
|
32
|
+
Requires-Dist: ai-agent-contracts>=0.3.0; extra == 'all'
|
|
33
|
+
Requires-Dist: claude-agent-sdk>=0.1.58; extra == 'all'
|
|
34
34
|
Requires-Dist: duckdb; extra == 'all'
|
|
35
35
|
Requires-Dist: google-cloud-bigquery; extra == 'all'
|
|
36
36
|
Requires-Dist: psycopg2-binary; extra == 'all'
|
|
@@ -39,10 +39,10 @@ Provides-Extra: bigquery
|
|
|
39
39
|
Requires-Dist: google-cloud-bigquery; extra == 'bigquery'
|
|
40
40
|
Provides-Extra: dev
|
|
41
41
|
Requires-Dist: duckdb; extra == 'dev'
|
|
42
|
-
Requires-Dist: pytest-asyncio>=0
|
|
43
|
-
Requires-Dist: pytest-cov>=
|
|
44
|
-
Requires-Dist: pytest>=
|
|
45
|
-
Requires-Dist: ruff>=0.
|
|
42
|
+
Requires-Dist: pytest-asyncio>=1.3.0; extra == 'dev'
|
|
43
|
+
Requires-Dist: pytest-cov>=7.1.0; extra == 'dev'
|
|
44
|
+
Requires-Dist: pytest>=9.0.3; extra == 'dev'
|
|
45
|
+
Requires-Dist: ruff>=0.15.10; extra == 'dev'
|
|
46
46
|
Provides-Extra: duckdb
|
|
47
47
|
Requires-Dist: duckdb; extra == 'duckdb'
|
|
48
48
|
Provides-Extra: postgres
|
|
@@ -58,7 +58,7 @@ Description-Content-Type: text/markdown
|
|
|
58
58
|
[](https://www.python.org/downloads/)
|
|
59
59
|
[](https://opensource.org/licenses/MIT)
|
|
60
60
|
|
|
61
|
-
**
|
|
61
|
+
**YAML-first, domain-driven data governance for AI agents.**
|
|
62
62
|
|
|
63
63
|
`agentic-data-contracts` takes a domain-driven approach to AI agent governance: instead of letting agents figure out your data landscape by trial and error, you teach them your business domains, metrics, and rules upfront — in YAML. The agent starts by understanding *what* a business domain means, then discovers *which* metrics to use, then builds queries that comply with your governance rules. All enforced automatically at query time via SQL validation powered by [sqlglot](https://github.com/tobymao/sqlglot).
|
|
64
64
|
|
|
@@ -68,6 +68,19 @@ Description-Content-Type: text/markdown
|
|
|
68
68
|
|
|
69
69
|
## How It Works
|
|
70
70
|
|
|
71
|
+
The agent follows a domain-driven workflow — understanding business context before writing SQL:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
1. Agent receives: "How is revenue trending?"
|
|
75
|
+
2. lookup_domain("revenue") → "Revenue is recognized at fulfillment, not booking"
|
|
76
|
+
3. lookup_metric("total_revenue") → SUM(amount) FILTER (WHERE status = 'completed')
|
|
77
|
+
4. Agent writes SQL using the metric definition
|
|
78
|
+
5. validate_query(sql) → VALID (passes all contract rules)
|
|
79
|
+
6. run_query(sql) → results returned
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Governance rules are enforced automatically at query time:
|
|
83
|
+
|
|
71
84
|
```
|
|
72
85
|
Agent: "SELECT * FROM analytics.orders"
|
|
73
86
|
-> BLOCKED (no SELECT * — specify explicit columns)
|
|
@@ -77,12 +90,9 @@ Agent: "SELECT order_id, amount FROM analytics.orders"
|
|
|
77
90
|
|
|
78
91
|
Agent: "SELECT order_id, amount FROM analytics.orders WHERE tenant_id = 'acme'"
|
|
79
92
|
-> PASSED + WARN (consider using semantic revenue definition)
|
|
80
|
-
|
|
81
|
-
Agent: "DELETE FROM analytics.orders WHERE id = 1"
|
|
82
|
-
-> BLOCKED (forbidden operation: DELETE)
|
|
83
93
|
```
|
|
84
94
|
|
|
85
|
-
The contract defines the rules. The library enforces them — before the query ever reaches the database.
|
|
95
|
+
The contract defines the domains, metrics, and rules. The library enforces them — before the query ever reaches the database.
|
|
86
96
|
|
|
87
97
|
## Installation
|
|
88
98
|
|
|
@@ -123,6 +133,13 @@ semantic:
|
|
|
123
133
|
- schema: marketing
|
|
124
134
|
tables: [campaigns] # or list specific tables
|
|
125
135
|
forbidden_operations: [DELETE, DROP, TRUNCATE, UPDATE, INSERT]
|
|
136
|
+
domains:
|
|
137
|
+
- name: revenue
|
|
138
|
+
summary: "Financial metrics from completed orders"
|
|
139
|
+
description: >
|
|
140
|
+
Revenue is recognized at fulfillment, not at booking.
|
|
141
|
+
Excludes refunds and chargebacks unless stated.
|
|
142
|
+
metrics: [total_revenue]
|
|
126
143
|
rules:
|
|
127
144
|
- name: tenant_isolation
|
|
128
145
|
description: "All queries must filter by tenant_id"
|
|
@@ -216,7 +233,7 @@ asyncio.run(demo())
|
|
|
216
233
|
|
|
217
234
|
| Tool | Description |
|
|
218
235
|
|------|-------------|
|
|
219
|
-
| `list_schemas` | List
|
|
236
|
+
| `list_schemas` | List allowed schemas with descriptions and preferred flags |
|
|
220
237
|
| `list_tables` | List allowed tables, optionally filtered by schema |
|
|
221
238
|
| `describe_table` | Get full column details for an allowed table |
|
|
222
239
|
| `preview_table` | Preview sample rows from an allowed table |
|
|
@@ -229,6 +246,57 @@ asyncio.run(demo())
|
|
|
229
246
|
| `run_query` | Validate and execute a SQL query, returning results |
|
|
230
247
|
| `get_contract_info` | Get the full contract: rules, limits, domains, and session status |
|
|
231
248
|
|
|
249
|
+
## Domain-Driven Agent Workflow
|
|
250
|
+
|
|
251
|
+
The core design principle: **agents should understand the business domain before writing SQL.** Instead of dumping table schemas and hoping for the best, the contract teaches the agent your business vocabulary through progressive disclosure:
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
1. Domain context → "What does 'revenue' mean here?"
|
|
255
|
+
2. Metric definitions → "How is 'total_revenue' calculated?"
|
|
256
|
+
3. Query execution → "Run the validated SQL"
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Defining domains
|
|
260
|
+
|
|
261
|
+
Each domain carries a description that teaches the agent your business rules — things the SQL alone can't express:
|
|
262
|
+
|
|
263
|
+
```yaml
|
|
264
|
+
semantic:
|
|
265
|
+
domains:
|
|
266
|
+
- name: acquisition
|
|
267
|
+
summary: "Customer acquisition costs and conversion metrics"
|
|
268
|
+
description: >
|
|
269
|
+
Acquisition metrics track the cost and efficiency of
|
|
270
|
+
acquiring new customers across all channels.
|
|
271
|
+
CAC is calculated using fully-loaded cost, not just ad spend.
|
|
272
|
+
metrics: [CAC, CPA, CPL, click_through_rate]
|
|
273
|
+
- name: retention
|
|
274
|
+
summary: "Customer retention, churn, and lifetime value"
|
|
275
|
+
description: >
|
|
276
|
+
Retention metrics measure how well we keep customers.
|
|
277
|
+
Churn is measured on a 30-day rolling window.
|
|
278
|
+
A customer is "active" if they had at least one qualifying
|
|
279
|
+
action in the window.
|
|
280
|
+
metrics: [churn_rate, LTV, retention_30d]
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### How the agent uses domains
|
|
284
|
+
|
|
285
|
+
The system prompt gives the agent a compact domain index. When a user asks a domain-specific question, the agent explores progressively:
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
lookup_domain("acquisition") → business context + metric descriptions
|
|
289
|
+
lookup_metric("CAC") → SQL expression, source table, filters
|
|
290
|
+
lookup_metric("acquisition cost") → fuzzy match, returns [CAC, CPA] as candidates
|
|
291
|
+
list_metrics(domain="retention") → all metrics in the retention domain
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
This means the agent knows that "revenue is recognized at fulfillment, not at booking" *before* it writes a single line of SQL — reducing hallucinated metrics and incorrect calculations.
|
|
295
|
+
|
|
296
|
+
### Why progressive disclosure works
|
|
297
|
+
|
|
298
|
+
This pattern — compact index in the prompt, detailed context on demand — is the same philosophy validated by agent skill systems, MCP tool servers, and RAG architectures. Instead of overloading the agent's context window with everything upfront, you give it just enough to know *where to look*, then let it pull details when needed. The result is better token efficiency, more focused reasoning, and fewer hallucinations from context overload.
|
|
299
|
+
|
|
232
300
|
## Contract Rules
|
|
233
301
|
|
|
234
302
|
Rules are enforced at three levels:
|
|
@@ -397,53 +465,6 @@ dc = DataContract.from_yaml("contract.yml")
|
|
|
397
465
|
print(dc.to_system_prompt(renderer=MarkdownRenderer()))
|
|
398
466
|
```
|
|
399
467
|
|
|
400
|
-
## Domain-Driven Agent Workflow
|
|
401
|
-
|
|
402
|
-
The core design principle: **agents should understand the business domain before writing SQL.** Instead of dumping table schemas and hoping for the best, the contract teaches the agent your business vocabulary through progressive disclosure:
|
|
403
|
-
|
|
404
|
-
```
|
|
405
|
-
1. Domain context → "What does 'revenue' mean here?"
|
|
406
|
-
2. Metric definitions → "How is 'total_revenue' calculated?"
|
|
407
|
-
3. Query execution → "Run the validated SQL"
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
### Defining domains
|
|
411
|
-
|
|
412
|
-
Each domain carries a description that teaches the agent your business rules — things the SQL alone can't express:
|
|
413
|
-
|
|
414
|
-
```yaml
|
|
415
|
-
semantic:
|
|
416
|
-
domains:
|
|
417
|
-
- name: acquisition
|
|
418
|
-
summary: "Customer acquisition costs and conversion metrics"
|
|
419
|
-
description: >
|
|
420
|
-
Acquisition metrics track the cost and efficiency of
|
|
421
|
-
acquiring new customers across all channels.
|
|
422
|
-
CAC is calculated using fully-loaded cost, not just ad spend.
|
|
423
|
-
metrics: [CAC, CPA, CPL, click_through_rate]
|
|
424
|
-
- name: retention
|
|
425
|
-
summary: "Customer retention, churn, and lifetime value"
|
|
426
|
-
description: >
|
|
427
|
-
Retention metrics measure how well we keep customers.
|
|
428
|
-
Churn is measured on a 30-day rolling window.
|
|
429
|
-
A customer is "active" if they had at least one qualifying
|
|
430
|
-
action in the window.
|
|
431
|
-
metrics: [churn_rate, LTV, retention_30d]
|
|
432
|
-
```
|
|
433
|
-
|
|
434
|
-
### How the agent uses domains
|
|
435
|
-
|
|
436
|
-
The system prompt gives the agent a compact domain index. When a user asks a domain-specific question, the agent explores progressively:
|
|
437
|
-
|
|
438
|
-
```
|
|
439
|
-
lookup_domain("acquisition") → business context + metric descriptions
|
|
440
|
-
lookup_metric("CAC") → SQL expression, source table, filters
|
|
441
|
-
lookup_metric("acquisition cost") → fuzzy match, returns [CAC, CPA] as candidates
|
|
442
|
-
list_metrics(domain="retention") → all metrics in the retention domain
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
This means the agent knows that "revenue is recognized at fulfillment, not at booking" *before* it writes a single line of SQL — reducing hallucinated metrics and incorrect calculations.
|
|
446
|
-
|
|
447
468
|
## Scaling to Large Organizations
|
|
448
469
|
|
|
449
470
|
Tested for 200+ tables, 300+ metrics, 50+ relationships across multiple schemas.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://www.python.org/downloads/)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
|
-
**
|
|
8
|
+
**YAML-first, domain-driven data governance for AI agents.**
|
|
9
9
|
|
|
10
10
|
`agentic-data-contracts` takes a domain-driven approach to AI agent governance: instead of letting agents figure out your data landscape by trial and error, you teach them your business domains, metrics, and rules upfront — in YAML. The agent starts by understanding *what* a business domain means, then discovers *which* metrics to use, then builds queries that comply with your governance rules. All enforced automatically at query time via SQL validation powered by [sqlglot](https://github.com/tobymao/sqlglot).
|
|
11
11
|
|
|
@@ -15,6 +15,19 @@
|
|
|
15
15
|
|
|
16
16
|
## How It Works
|
|
17
17
|
|
|
18
|
+
The agent follows a domain-driven workflow — understanding business context before writing SQL:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
1. Agent receives: "How is revenue trending?"
|
|
22
|
+
2. lookup_domain("revenue") → "Revenue is recognized at fulfillment, not booking"
|
|
23
|
+
3. lookup_metric("total_revenue") → SUM(amount) FILTER (WHERE status = 'completed')
|
|
24
|
+
4. Agent writes SQL using the metric definition
|
|
25
|
+
5. validate_query(sql) → VALID (passes all contract rules)
|
|
26
|
+
6. run_query(sql) → results returned
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Governance rules are enforced automatically at query time:
|
|
30
|
+
|
|
18
31
|
```
|
|
19
32
|
Agent: "SELECT * FROM analytics.orders"
|
|
20
33
|
-> BLOCKED (no SELECT * — specify explicit columns)
|
|
@@ -24,12 +37,9 @@ Agent: "SELECT order_id, amount FROM analytics.orders"
|
|
|
24
37
|
|
|
25
38
|
Agent: "SELECT order_id, amount FROM analytics.orders WHERE tenant_id = 'acme'"
|
|
26
39
|
-> PASSED + WARN (consider using semantic revenue definition)
|
|
27
|
-
|
|
28
|
-
Agent: "DELETE FROM analytics.orders WHERE id = 1"
|
|
29
|
-
-> BLOCKED (forbidden operation: DELETE)
|
|
30
40
|
```
|
|
31
41
|
|
|
32
|
-
The contract defines the rules. The library enforces them — before the query ever reaches the database.
|
|
42
|
+
The contract defines the domains, metrics, and rules. The library enforces them — before the query ever reaches the database.
|
|
33
43
|
|
|
34
44
|
## Installation
|
|
35
45
|
|
|
@@ -70,6 +80,13 @@ semantic:
|
|
|
70
80
|
- schema: marketing
|
|
71
81
|
tables: [campaigns] # or list specific tables
|
|
72
82
|
forbidden_operations: [DELETE, DROP, TRUNCATE, UPDATE, INSERT]
|
|
83
|
+
domains:
|
|
84
|
+
- name: revenue
|
|
85
|
+
summary: "Financial metrics from completed orders"
|
|
86
|
+
description: >
|
|
87
|
+
Revenue is recognized at fulfillment, not at booking.
|
|
88
|
+
Excludes refunds and chargebacks unless stated.
|
|
89
|
+
metrics: [total_revenue]
|
|
73
90
|
rules:
|
|
74
91
|
- name: tenant_isolation
|
|
75
92
|
description: "All queries must filter by tenant_id"
|
|
@@ -163,7 +180,7 @@ asyncio.run(demo())
|
|
|
163
180
|
|
|
164
181
|
| Tool | Description |
|
|
165
182
|
|------|-------------|
|
|
166
|
-
| `list_schemas` | List
|
|
183
|
+
| `list_schemas` | List allowed schemas with descriptions and preferred flags |
|
|
167
184
|
| `list_tables` | List allowed tables, optionally filtered by schema |
|
|
168
185
|
| `describe_table` | Get full column details for an allowed table |
|
|
169
186
|
| `preview_table` | Preview sample rows from an allowed table |
|
|
@@ -176,6 +193,57 @@ asyncio.run(demo())
|
|
|
176
193
|
| `run_query` | Validate and execute a SQL query, returning results |
|
|
177
194
|
| `get_contract_info` | Get the full contract: rules, limits, domains, and session status |
|
|
178
195
|
|
|
196
|
+
## Domain-Driven Agent Workflow
|
|
197
|
+
|
|
198
|
+
The core design principle: **agents should understand the business domain before writing SQL.** Instead of dumping table schemas and hoping for the best, the contract teaches the agent your business vocabulary through progressive disclosure:
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
1. Domain context → "What does 'revenue' mean here?"
|
|
202
|
+
2. Metric definitions → "How is 'total_revenue' calculated?"
|
|
203
|
+
3. Query execution → "Run the validated SQL"
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Defining domains
|
|
207
|
+
|
|
208
|
+
Each domain carries a description that teaches the agent your business rules — things the SQL alone can't express:
|
|
209
|
+
|
|
210
|
+
```yaml
|
|
211
|
+
semantic:
|
|
212
|
+
domains:
|
|
213
|
+
- name: acquisition
|
|
214
|
+
summary: "Customer acquisition costs and conversion metrics"
|
|
215
|
+
description: >
|
|
216
|
+
Acquisition metrics track the cost and efficiency of
|
|
217
|
+
acquiring new customers across all channels.
|
|
218
|
+
CAC is calculated using fully-loaded cost, not just ad spend.
|
|
219
|
+
metrics: [CAC, CPA, CPL, click_through_rate]
|
|
220
|
+
- name: retention
|
|
221
|
+
summary: "Customer retention, churn, and lifetime value"
|
|
222
|
+
description: >
|
|
223
|
+
Retention metrics measure how well we keep customers.
|
|
224
|
+
Churn is measured on a 30-day rolling window.
|
|
225
|
+
A customer is "active" if they had at least one qualifying
|
|
226
|
+
action in the window.
|
|
227
|
+
metrics: [churn_rate, LTV, retention_30d]
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### How the agent uses domains
|
|
231
|
+
|
|
232
|
+
The system prompt gives the agent a compact domain index. When a user asks a domain-specific question, the agent explores progressively:
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
lookup_domain("acquisition") → business context + metric descriptions
|
|
236
|
+
lookup_metric("CAC") → SQL expression, source table, filters
|
|
237
|
+
lookup_metric("acquisition cost") → fuzzy match, returns [CAC, CPA] as candidates
|
|
238
|
+
list_metrics(domain="retention") → all metrics in the retention domain
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
This means the agent knows that "revenue is recognized at fulfillment, not at booking" *before* it writes a single line of SQL — reducing hallucinated metrics and incorrect calculations.
|
|
242
|
+
|
|
243
|
+
### Why progressive disclosure works
|
|
244
|
+
|
|
245
|
+
This pattern — compact index in the prompt, detailed context on demand — is the same philosophy validated by agent skill systems, MCP tool servers, and RAG architectures. Instead of overloading the agent's context window with everything upfront, you give it just enough to know *where to look*, then let it pull details when needed. The result is better token efficiency, more focused reasoning, and fewer hallucinations from context overload.
|
|
246
|
+
|
|
179
247
|
## Contract Rules
|
|
180
248
|
|
|
181
249
|
Rules are enforced at three levels:
|
|
@@ -344,53 +412,6 @@ dc = DataContract.from_yaml("contract.yml")
|
|
|
344
412
|
print(dc.to_system_prompt(renderer=MarkdownRenderer()))
|
|
345
413
|
```
|
|
346
414
|
|
|
347
|
-
## Domain-Driven Agent Workflow
|
|
348
|
-
|
|
349
|
-
The core design principle: **agents should understand the business domain before writing SQL.** Instead of dumping table schemas and hoping for the best, the contract teaches the agent your business vocabulary through progressive disclosure:
|
|
350
|
-
|
|
351
|
-
```
|
|
352
|
-
1. Domain context → "What does 'revenue' mean here?"
|
|
353
|
-
2. Metric definitions → "How is 'total_revenue' calculated?"
|
|
354
|
-
3. Query execution → "Run the validated SQL"
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
### Defining domains
|
|
358
|
-
|
|
359
|
-
Each domain carries a description that teaches the agent your business rules — things the SQL alone can't express:
|
|
360
|
-
|
|
361
|
-
```yaml
|
|
362
|
-
semantic:
|
|
363
|
-
domains:
|
|
364
|
-
- name: acquisition
|
|
365
|
-
summary: "Customer acquisition costs and conversion metrics"
|
|
366
|
-
description: >
|
|
367
|
-
Acquisition metrics track the cost and efficiency of
|
|
368
|
-
acquiring new customers across all channels.
|
|
369
|
-
CAC is calculated using fully-loaded cost, not just ad spend.
|
|
370
|
-
metrics: [CAC, CPA, CPL, click_through_rate]
|
|
371
|
-
- name: retention
|
|
372
|
-
summary: "Customer retention, churn, and lifetime value"
|
|
373
|
-
description: >
|
|
374
|
-
Retention metrics measure how well we keep customers.
|
|
375
|
-
Churn is measured on a 30-day rolling window.
|
|
376
|
-
A customer is "active" if they had at least one qualifying
|
|
377
|
-
action in the window.
|
|
378
|
-
metrics: [churn_rate, LTV, retention_30d]
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
### How the agent uses domains
|
|
382
|
-
|
|
383
|
-
The system prompt gives the agent a compact domain index. When a user asks a domain-specific question, the agent explores progressively:
|
|
384
|
-
|
|
385
|
-
```
|
|
386
|
-
lookup_domain("acquisition") → business context + metric descriptions
|
|
387
|
-
lookup_metric("CAC") → SQL expression, source table, filters
|
|
388
|
-
lookup_metric("acquisition cost") → fuzzy match, returns [CAC, CPA] as candidates
|
|
389
|
-
list_metrics(domain="retention") → all metrics in the retention domain
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
This means the agent knows that "revenue is recognized at fulfillment, not at booking" *before* it writes a single line of SQL — reducing hallucinated metrics and incorrect calculations.
|
|
393
|
-
|
|
394
415
|
## Scaling to Large Organizations
|
|
395
416
|
|
|
396
417
|
Tested for 200+ tables, 300+ metrics, 50+ relationships across multiple schemas.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Agentic Data Contracts — Architecture
|
|
2
2
|
|
|
3
3
|
**Date:** 2026-04-13
|
|
4
|
-
**Status:** Implemented (v0.9.
|
|
4
|
+
**Status:** Implemented (v0.9.2)
|
|
5
5
|
**Author:** Qing Ye + Claude
|
|
6
6
|
|
|
7
7
|
## Problem Statement
|
|
@@ -188,7 +188,7 @@ When `ai-agent-contracts` is NOT installed, `ContractSession` provides self-cont
|
|
|
188
188
|
|
|
189
189
|
- **Retry count** — incremented on each failed query attempt, checked against `max_retries`
|
|
190
190
|
- **Token usage** — tracked via callback, checked against `token_budget`
|
|
191
|
-
- **Wall-clock duration** — start
|
|
191
|
+
- **Wall-clock duration** — lazy start on first `check_limits()` call (not at construction), checked against `max_duration_seconds`. Can be reset via `reset_timer()` for frameworks that manage their own idle timeouts.
|
|
192
192
|
- **Cost estimate** — if EXPLAIN adapter returns cost info, checked against `cost_limit_usd`
|
|
193
193
|
|
|
194
194
|
These are simple counters/timers with guard checks before each tool call. No formal state machine.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "agentic-data-contracts"
|
|
3
|
-
version = "0.9.
|
|
4
|
-
description = "YAML-first data
|
|
3
|
+
version = "0.9.2"
|
|
4
|
+
description = "YAML-first, domain-driven data governance for AI agents"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
7
7
|
authors = [{ name = "Qing", email = "qingye779@gmail.com" }]
|
|
@@ -10,9 +10,11 @@ keywords = [
|
|
|
10
10
|
"ai-agents",
|
|
11
11
|
"data-contracts",
|
|
12
12
|
"data-governance",
|
|
13
|
+
"domain-driven",
|
|
13
14
|
"sql-validation",
|
|
14
15
|
"llm",
|
|
15
16
|
"claude",
|
|
17
|
+
"semantic-layer",
|
|
16
18
|
"analytics",
|
|
17
19
|
"dbt",
|
|
18
20
|
]
|
|
@@ -30,7 +32,7 @@ classifiers = [
|
|
|
30
32
|
dependencies = [
|
|
31
33
|
"sqlglot>=23.0",
|
|
32
34
|
"pydantic>=2.0",
|
|
33
|
-
"pyyaml>=6.0",
|
|
35
|
+
"pyyaml>=6.0.3",
|
|
34
36
|
"thefuzz>=0.22.1",
|
|
35
37
|
]
|
|
36
38
|
|
|
@@ -41,17 +43,17 @@ Issues = "https://github.com/flyersworder/agentic-data-contracts/issues"
|
|
|
41
43
|
Documentation = "https://github.com/flyersworder/agentic-data-contracts/blob/main/docs/architecture.md"
|
|
42
44
|
|
|
43
45
|
[project.optional-dependencies]
|
|
44
|
-
agent-sdk = ["claude-agent-sdk>=0.1.
|
|
45
|
-
agent-contracts = ["ai-agent-contracts>=0.
|
|
46
|
+
agent-sdk = ["claude-agent-sdk>=0.1.58"]
|
|
47
|
+
agent-contracts = ["ai-agent-contracts>=0.3.0"]
|
|
46
48
|
bigquery = ["google-cloud-bigquery"]
|
|
47
49
|
snowflake = ["snowflake-connector-python"]
|
|
48
50
|
postgres = ["psycopg2-binary"]
|
|
49
51
|
duckdb = ["duckdb"]
|
|
50
52
|
dev = [
|
|
51
|
-
"pytest>=
|
|
52
|
-
"pytest-asyncio>=0
|
|
53
|
-
"pytest-cov>=
|
|
54
|
-
"ruff>=0.
|
|
53
|
+
"pytest>=9.0.3",
|
|
54
|
+
"pytest-asyncio>=1.3.0",
|
|
55
|
+
"pytest-cov>=7.1.0",
|
|
56
|
+
"ruff>=0.15.10",
|
|
55
57
|
"duckdb",
|
|
56
58
|
]
|
|
57
59
|
all = [
|
|
@@ -20,12 +20,23 @@ class ContractSession:
|
|
|
20
20
|
self.retries: int = 0
|
|
21
21
|
self.tokens_used: int = 0
|
|
22
22
|
self.cost_usd: float = 0.0
|
|
23
|
-
self._start_time: float =
|
|
23
|
+
self._start_time: float | None = None
|
|
24
|
+
|
|
25
|
+
def _ensure_timer(self) -> None:
|
|
26
|
+
"""Start the timer if not already running."""
|
|
27
|
+
if self._start_time is None:
|
|
28
|
+
self._start_time = time.monotonic()
|
|
24
29
|
|
|
25
30
|
@property
|
|
26
31
|
def elapsed_seconds(self) -> float:
|
|
32
|
+
if self._start_time is None:
|
|
33
|
+
return 0.0
|
|
27
34
|
return time.monotonic() - self._start_time
|
|
28
35
|
|
|
36
|
+
def reset_timer(self) -> None:
|
|
37
|
+
"""Reset the timer so it restarts on the next check_limits() call."""
|
|
38
|
+
self._start_time = None
|
|
39
|
+
|
|
29
40
|
def record_retry(self) -> None:
|
|
30
41
|
self.retries += 1
|
|
31
42
|
|
|
@@ -36,6 +47,7 @@ class ContractSession:
|
|
|
36
47
|
self.cost_usd += amount
|
|
37
48
|
|
|
38
49
|
def check_limits(self) -> None:
|
|
50
|
+
self._ensure_timer()
|
|
39
51
|
res = self.contract.schema.resources
|
|
40
52
|
if res is None:
|
|
41
53
|
return
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_core/test_session.py
RENAMED
|
@@ -60,6 +60,7 @@ def test_session_blocks_on_cost_limit(fixtures_dir: Path) -> None:
|
|
|
60
60
|
def test_session_elapsed_seconds(fixtures_dir: Path) -> None:
|
|
61
61
|
dc = DataContract.from_yaml(fixtures_dir / "valid_contract.yml")
|
|
62
62
|
session = ContractSession(dc)
|
|
63
|
+
session.check_limits() # starts the timer
|
|
63
64
|
assert session.elapsed_seconds >= 0.0
|
|
64
65
|
|
|
65
66
|
|
|
@@ -85,3 +86,61 @@ def test_session_remaining_budget(fixtures_dir: Path) -> None:
|
|
|
85
86
|
assert info["retries_remaining"] == 2
|
|
86
87
|
assert info["tokens_remaining"] == 40000
|
|
87
88
|
assert info["cost_remaining_usd"] == pytest.approx(3.50)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# --- Lazy timer tests (issue #16) ---
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_timer_not_started_at_construction(fixtures_dir: Path) -> None:
|
|
95
|
+
"""Timer should not start at construction — elapsed should be 0."""
|
|
96
|
+
dc = DataContract.from_yaml(fixtures_dir / "valid_contract.yml")
|
|
97
|
+
session = ContractSession(dc)
|
|
98
|
+
assert session.elapsed_seconds == 0.0
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_timer_starts_on_first_check_limits(fixtures_dir: Path) -> None:
|
|
102
|
+
"""Timer should start lazily on the first check_limits() call."""
|
|
103
|
+
dc = DataContract.from_yaml(fixtures_dir / "valid_contract.yml")
|
|
104
|
+
session = ContractSession(dc)
|
|
105
|
+
assert session._start_time is None
|
|
106
|
+
session.check_limits()
|
|
107
|
+
assert session._start_time is not None
|
|
108
|
+
assert session.elapsed_seconds >= 0.0
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_reset_timer(fixtures_dir: Path) -> None:
|
|
112
|
+
"""reset_timer() should clear the timer so elapsed returns 0."""
|
|
113
|
+
dc = DataContract.from_yaml(fixtures_dir / "valid_contract.yml")
|
|
114
|
+
session = ContractSession(dc)
|
|
115
|
+
session.check_limits() # starts timer
|
|
116
|
+
session.reset_timer()
|
|
117
|
+
assert session.elapsed_seconds == 0.0
|
|
118
|
+
assert session._start_time is None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_reset_timer_restarts_on_next_check(fixtures_dir: Path) -> None:
|
|
122
|
+
"""After reset, timer should restart on the next check_limits() call."""
|
|
123
|
+
dc = DataContract.from_yaml(fixtures_dir / "valid_contract.yml")
|
|
124
|
+
session = ContractSession(dc)
|
|
125
|
+
session.check_limits() # starts timer
|
|
126
|
+
session.reset_timer()
|
|
127
|
+
session.check_limits() # restarts timer
|
|
128
|
+
assert session._start_time is not None
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_reset_timer_before_started_is_noop(fixtures_dir: Path) -> None:
|
|
132
|
+
"""reset_timer() on an unstarted timer should be a safe no-op."""
|
|
133
|
+
dc = DataContract.from_yaml(fixtures_dir / "valid_contract.yml")
|
|
134
|
+
session = ContractSession(dc)
|
|
135
|
+
session.reset_timer() # should not raise
|
|
136
|
+
assert session._start_time is None
|
|
137
|
+
assert session.elapsed_seconds == 0.0
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def test_remaining_before_timer_started(fixtures_dir: Path) -> None:
|
|
141
|
+
"""remaining() should show full duration budget when timer not started."""
|
|
142
|
+
dc = DataContract.from_yaml(fixtures_dir / "valid_contract.yml")
|
|
143
|
+
session = ContractSession(dc)
|
|
144
|
+
info = session.remaining()
|
|
145
|
+
assert "seconds_remaining" in info
|
|
146
|
+
assert info["seconds_remaining"] == 300.0
|
|
@@ -8,12 +8,12 @@ resolution-markers = [
|
|
|
8
8
|
]
|
|
9
9
|
|
|
10
10
|
[options]
|
|
11
|
-
exclude-newer = "2026-04-
|
|
11
|
+
exclude-newer = "2026-04-12T16:17:12.933746Z"
|
|
12
12
|
exclude-newer-span = "P3D"
|
|
13
13
|
|
|
14
14
|
[[package]]
|
|
15
15
|
name = "agentic-data-contracts"
|
|
16
|
-
version = "0.9.
|
|
16
|
+
version = "0.9.2"
|
|
17
17
|
source = { editable = "." }
|
|
18
18
|
dependencies = [
|
|
19
19
|
{ name = "pydantic" },
|
|
@@ -60,18 +60,18 @@ snowflake = [
|
|
|
60
60
|
[package.metadata]
|
|
61
61
|
requires-dist = [
|
|
62
62
|
{ name = "agentic-data-contracts", extras = ["agent-sdk", "agent-contracts", "bigquery", "snowflake", "postgres", "duckdb"], marker = "extra == 'all'" },
|
|
63
|
-
{ name = "ai-agent-contracts", marker = "extra == 'agent-contracts'", specifier = ">=0.
|
|
64
|
-
{ name = "claude-agent-sdk", marker = "extra == 'agent-sdk'", specifier = ">=0.1.
|
|
63
|
+
{ name = "ai-agent-contracts", marker = "extra == 'agent-contracts'", specifier = ">=0.3.0" },
|
|
64
|
+
{ name = "claude-agent-sdk", marker = "extra == 'agent-sdk'", specifier = ">=0.1.58" },
|
|
65
65
|
{ name = "duckdb", marker = "extra == 'dev'" },
|
|
66
66
|
{ name = "duckdb", marker = "extra == 'duckdb'" },
|
|
67
67
|
{ name = "google-cloud-bigquery", marker = "extra == 'bigquery'" },
|
|
68
68
|
{ name = "psycopg2-binary", marker = "extra == 'postgres'" },
|
|
69
69
|
{ name = "pydantic", specifier = ">=2.0" },
|
|
70
|
-
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=
|
|
71
|
-
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0
|
|
72
|
-
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=
|
|
73
|
-
{ name = "pyyaml", specifier = ">=6.0" },
|
|
74
|
-
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.
|
|
70
|
+
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=9.0.3" },
|
|
71
|
+
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=1.3.0" },
|
|
72
|
+
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=7.1.0" },
|
|
73
|
+
{ name = "pyyaml", specifier = ">=6.0.3" },
|
|
74
|
+
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.15.10" },
|
|
75
75
|
{ name = "snowflake-connector-python", marker = "extra == 'snowflake'" },
|
|
76
76
|
{ name = "sqlglot", specifier = ">=23.0" },
|
|
77
77
|
{ name = "thefuzz", specifier = ">=0.22.1" },
|
|
@@ -2202,27 +2202,27 @@ wheels = [
|
|
|
2202
2202
|
|
|
2203
2203
|
[[package]]
|
|
2204
2204
|
name = "ruff"
|
|
2205
|
-
version = "0.15.
|
|
2206
|
-
source = { registry = "https://pypi.org/simple" }
|
|
2207
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
2208
|
-
wheels = [
|
|
2209
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2210
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2211
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2212
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2213
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2214
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2215
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2216
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2217
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2218
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2219
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2220
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2221
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2222
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2223
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2224
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2225
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2205
|
+
version = "0.15.10"
|
|
2206
|
+
source = { registry = "https://pypi.org/simple" }
|
|
2207
|
+
sdist = { url = "https://files.pythonhosted.org/packages/e7/d9/aa3f7d59a10ef6b14fe3431706f854dbf03c5976be614a9796d36326810c/ruff-0.15.10.tar.gz", hash = "sha256:d1f86e67ebfdef88e00faefa1552b5e510e1d35f3be7d423dc7e84e63788c94e", size = 4631728, upload-time = "2026-04-09T14:06:09.884Z" }
|
|
2208
|
+
wheels = [
|
|
2209
|
+
{ url = "https://files.pythonhosted.org/packages/eb/00/a1c2fdc9939b2c03691edbda290afcd297f1f389196172826b03d6b6a595/ruff-0.15.10-py3-none-linux_armv6l.whl", hash = "sha256:0744e31482f8f7d0d10a11fcbf897af272fefdfcb10f5af907b18c2813ff4d5f", size = 10563362, upload-time = "2026-04-09T14:06:21.189Z" },
|
|
2210
|
+
{ url = "https://files.pythonhosted.org/packages/5c/15/006990029aea0bebe9d33c73c3e28c80c391ebdba408d1b08496f00d422d/ruff-0.15.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b1e7c16ea0ff5a53b7c2df52d947e685973049be1cdfe2b59a9c43601897b22e", size = 10951122, upload-time = "2026-04-09T14:06:02.236Z" },
|
|
2211
|
+
{ url = "https://files.pythonhosted.org/packages/f2/c0/4ac978fe874d0618c7da647862afe697b281c2806f13ce904ad652fa87e4/ruff-0.15.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:93cc06a19e5155b4441dd72808fdf84290d84ad8a39ca3b0f994363ade4cebb1", size = 10314005, upload-time = "2026-04-09T14:06:00.026Z" },
|
|
2212
|
+
{ url = "https://files.pythonhosted.org/packages/da/73/c209138a5c98c0d321266372fc4e33ad43d506d7e5dd817dd89b60a8548f/ruff-0.15.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83e1dd04312997c99ea6965df66a14fb4f03ba978564574ffc68b0d61fd3989e", size = 10643450, upload-time = "2026-04-09T14:05:42.137Z" },
|
|
2213
|
+
{ url = "https://files.pythonhosted.org/packages/ec/76/0deec355d8ec10709653635b1f90856735302cb8e149acfdf6f82a5feb70/ruff-0.15.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8154d43684e4333360fedd11aaa40b1b08a4e37d8ffa9d95fee6fa5b37b6fab1", size = 10379597, upload-time = "2026-04-09T14:05:49.984Z" },
|
|
2214
|
+
{ url = "https://files.pythonhosted.org/packages/dc/be/86bba8fc8798c081e28a4b3bb6d143ccad3fd5f6f024f02002b8f08a9fa3/ruff-0.15.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ab88715f3a6deb6bde6c227f3a123410bec7b855c3ae331b4c006189e895cef", size = 11146645, upload-time = "2026-04-09T14:06:12.246Z" },
|
|
2215
|
+
{ url = "https://files.pythonhosted.org/packages/a8/89/140025e65911b281c57be1d385ba1d932c2366ca88ae6663685aed8d4881/ruff-0.15.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a768ff5969b4f44c349d48edf4ab4f91eddb27fd9d77799598e130fb628aa158", size = 12030289, upload-time = "2026-04-09T14:06:04.776Z" },
|
|
2216
|
+
{ url = "https://files.pythonhosted.org/packages/88/de/ddacca9545a5e01332567db01d44bd8cf725f2db3b3d61a80550b48308ea/ruff-0.15.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ee3ef42dab7078bda5ff6a1bcba8539e9857deb447132ad5566a038674540d0", size = 11496266, upload-time = "2026-04-09T14:05:55.485Z" },
|
|
2217
|
+
{ url = "https://files.pythonhosted.org/packages/bc/bb/7ddb00a83760ff4a83c4e2fc231fd63937cc7317c10c82f583302e0f6586/ruff-0.15.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51cb8cc943e891ba99989dd92d61e29b1d231e14811db9be6440ecf25d5c1609", size = 11256418, upload-time = "2026-04-09T14:05:57.69Z" },
|
|
2218
|
+
{ url = "https://files.pythonhosted.org/packages/dc/8d/55de0d35aacf6cd50b6ee91ee0f291672080021896543776f4170fc5c454/ruff-0.15.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e59c9bdc056a320fb9ea1700a8d591718b8faf78af065484e801258d3a76bc3f", size = 11288416, upload-time = "2026-04-09T14:05:44.695Z" },
|
|
2219
|
+
{ url = "https://files.pythonhosted.org/packages/68/cf/9438b1a27426ec46a80e0a718093c7f958ef72f43eb3111862949ead3cc1/ruff-0.15.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:136c00ca2f47b0018b073f28cb5c1506642a830ea941a60354b0e8bc8076b151", size = 10621053, upload-time = "2026-04-09T14:05:52.782Z" },
|
|
2220
|
+
{ url = "https://files.pythonhosted.org/packages/4c/50/e29be6e2c135e9cd4cb15fbade49d6a2717e009dff3766dd080fcb82e251/ruff-0.15.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8b80a2f3c9c8a950d6237f2ca12b206bccff626139be9fa005f14feb881a1ae8", size = 10378302, upload-time = "2026-04-09T14:06:14.361Z" },
|
|
2221
|
+
{ url = "https://files.pythonhosted.org/packages/18/2f/e0b36a6f99c51bb89f3a30239bc7bf97e87a37ae80aa2d6542d6e5150364/ruff-0.15.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e3e53c588164dc025b671c9df2462429d60357ea91af7e92e9d56c565a9f1b07", size = 10850074, upload-time = "2026-04-09T14:06:16.581Z" },
|
|
2222
|
+
{ url = "https://files.pythonhosted.org/packages/11/08/874da392558ce087a0f9b709dc6ec0d60cbc694c1c772dab8d5f31efe8cb/ruff-0.15.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b0c52744cf9f143a393e284125d2576140b68264a93c6716464e129a3e9adb48", size = 11358051, upload-time = "2026-04-09T14:06:18.948Z" },
|
|
2223
|
+
{ url = "https://files.pythonhosted.org/packages/e4/46/602938f030adfa043e67112b73821024dc79f3ab4df5474c25fa4c1d2d14/ruff-0.15.10-py3-none-win32.whl", hash = "sha256:d4272e87e801e9a27a2e8df7b21011c909d9ddd82f4f3281d269b6ba19789ca5", size = 10588964, upload-time = "2026-04-09T14:06:07.14Z" },
|
|
2224
|
+
{ url = "https://files.pythonhosted.org/packages/25/b6/261225b875d7a13b33a6d02508c39c28450b2041bb01d0f7f1a83d569512/ruff-0.15.10-py3-none-win_amd64.whl", hash = "sha256:28cb32d53203242d403d819fd6983152489b12e4a3ae44993543d6fe62ab42ed", size = 11745044, upload-time = "2026-04-09T14:05:39.473Z" },
|
|
2225
|
+
{ url = "https://files.pythonhosted.org/packages/58/ed/dea90a65b7d9e69888890fb14c90d7f51bf0c1e82ad800aeb0160e4bacfd/ruff-0.15.10-py3-none-win_arm64.whl", hash = "sha256:601d1610a9e1f1c2165a4f561eeaa2e2ea1e97f3287c5aa258d3dab8b57c6188", size = 11035607, upload-time = "2026-04-09T14:05:47.593Z" },
|
|
2226
2226
|
]
|
|
2227
2227
|
|
|
2228
2228
|
[[package]]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/examples/revenue_agent/agent.py
RENAMED
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/examples/revenue_agent/contract.yml
RENAMED
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/examples/revenue_agent/semantic.yml
RENAMED
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/examples/revenue_agent/setup_db.py
RENAMED
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/src/agentic_data_contracts/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/fixtures/minimal_contract.yml
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/fixtures/sample_cube_schema.yml
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/fixtures/semantic_source.yml
RENAMED
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/fixtures/valid_contract.yml
RENAMED
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_adapters/__init__.py
RENAMED
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_adapters/test_duckdb.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_bridge/test_compiler.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_core/test_contract.py
RENAMED
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_core/test_domain_model.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_core/test_scalability.py
RENAMED
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_core/test_schema.py
RENAMED
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_core/test_sdk_config.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_semantic/__init__.py
RENAMED
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_semantic/test_cube.py
RENAMED
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_semantic/test_dbt.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_semantic/test_search.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_tools/test_auto_load.py
RENAMED
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_tools/test_factory.py
RENAMED
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_tools/test_middleware.py
RENAMED
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_tools/test_pagination.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_validation/__init__.py
RENAMED
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_validation/test_checkers.py
RENAMED
|
File without changes
|
{agentic_data_contracts-0.9.1 → agentic_data_contracts-0.9.2}/tests/test_validation/test_explain.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|