aeo-validator-service 0.1.1__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.
- aeo_validator_service-0.1.1/.github/workflows/ci.yml +41 -0
- aeo_validator_service-0.1.1/.github/workflows/publish.yml +35 -0
- aeo_validator_service-0.1.1/.gitignore +12 -0
- aeo_validator_service-0.1.1/LICENSE +21 -0
- aeo_validator_service-0.1.1/PKG-INFO +193 -0
- aeo_validator_service-0.1.1/README.md +156 -0
- aeo_validator_service-0.1.1/pyproject.toml +79 -0
- aeo_validator_service-0.1.1/src/aeo_validator_service/__init__.py +45 -0
- aeo_validator_service-0.1.1/src/aeo_validator_service/__main__.py +17 -0
- aeo_validator_service-0.1.1/src/aeo_validator_service/app.py +300 -0
- aeo_validator_service-0.1.1/src/aeo_validator_service/audit_stream.py +86 -0
- aeo_validator_service-0.1.1/src/aeo_validator_service/drift.py +68 -0
- aeo_validator_service-0.1.1/src/aeo_validator_service/fetcher.py +76 -0
- aeo_validator_service-0.1.1/src/aeo_validator_service/models.py +87 -0
- aeo_validator_service-0.1.1/src/aeo_validator_service/validator.py +203 -0
- aeo_validator_service-0.1.1/src/aeo_validator_service/watch_store.py +86 -0
- aeo_validator_service-0.1.1/tests/__init__.py +0 -0
- aeo_validator_service-0.1.1/tests/test_app.py +200 -0
- aeo_validator_service-0.1.1/tests/test_audit_stream.py +107 -0
- aeo_validator_service-0.1.1/tests/test_drift.py +74 -0
- aeo_validator_service-0.1.1/tests/test_validator.py +132 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: test (py${{ matrix.python-version }})
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: false
|
|
15
|
+
matrix:
|
|
16
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Set up Python
|
|
21
|
+
uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: ${{ matrix.python-version }}
|
|
24
|
+
cache: pip
|
|
25
|
+
|
|
26
|
+
- name: Install
|
|
27
|
+
run: |
|
|
28
|
+
python -m pip install --upgrade pip
|
|
29
|
+
pip install -e ".[dev]"
|
|
30
|
+
|
|
31
|
+
- name: Lint
|
|
32
|
+
run: ruff check src tests
|
|
33
|
+
|
|
34
|
+
- name: Format check
|
|
35
|
+
run: ruff format --check src tests
|
|
36
|
+
|
|
37
|
+
- name: Type check
|
|
38
|
+
run: mypy src
|
|
39
|
+
|
|
40
|
+
- name: Test
|
|
41
|
+
run: pytest -v
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
name: Build + publish wheel + sdist
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
environment:
|
|
12
|
+
name: pypi
|
|
13
|
+
url: https://pypi.org/p/aeo-validator-service
|
|
14
|
+
permissions:
|
|
15
|
+
id-token: write
|
|
16
|
+
contents: read
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
with:
|
|
20
|
+
fetch-depth: 0
|
|
21
|
+
- uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.13"
|
|
24
|
+
- name: Install build tools
|
|
25
|
+
run: |
|
|
26
|
+
python -m pip install --upgrade pip
|
|
27
|
+
python -m pip install build
|
|
28
|
+
- name: Build distributions
|
|
29
|
+
run: python -m build
|
|
30
|
+
- name: Inspect distributions
|
|
31
|
+
run: ls -la dist/
|
|
32
|
+
- name: Publish to PyPI
|
|
33
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
34
|
+
with:
|
|
35
|
+
attestations: true
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Miz Causevic / Kinetic Gain
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aeo-validator-service
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Always-on validator service for AEO + Kinetic Gain Protocol Suite documents. Validate by URL, track content-hash drift, schedule re-fetch, emit structured diffs. The fourth layer of the AEO Reference Stack. Optional audit-stream-py integration via AUDIT_STREAM_URL env var.
|
|
5
|
+
Project-URL: Homepage, https://github.com/mizcausevic-dev/aeo-validator-service
|
|
6
|
+
Project-URL: Repository, https://github.com/mizcausevic-dev/aeo-validator-service
|
|
7
|
+
Project-URL: Issues, https://github.com/mizcausevic-dev/aeo-validator-service/issues
|
|
8
|
+
Project-URL: AEO Spec, https://github.com/mizcausevic-dev/aeo-protocol-spec
|
|
9
|
+
Project-URL: Author Site, https://kineticgain.com/
|
|
10
|
+
Author-email: Miz Causevic <miz@kineticgain.com>
|
|
11
|
+
License: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: aeo,drift,fastapi,kinetic-gain-protocol-suite,validator
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Framework :: FastAPI
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
24
|
+
Classifier: Topic :: System :: Monitoring
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.11
|
|
27
|
+
Requires-Dist: fastapi>=0.115
|
|
28
|
+
Requires-Dist: httpx>=0.27
|
|
29
|
+
Requires-Dist: pydantic>=2.7
|
|
30
|
+
Requires-Dist: uvicorn[standard]>=0.30
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest>=8.2; extra == 'dev'
|
|
35
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# aeo-validator-service
|
|
39
|
+
|
|
40
|
+
[](https://github.com/mizcausevic-dev/aeo-validator-service/actions/workflows/ci.yml)
|
|
41
|
+
[](https://www.python.org/)
|
|
42
|
+
[](LICENSE)
|
|
43
|
+
|
|
44
|
+
**Always-on validator service for AEO and the rest of the Kinetic Gain Protocol Suite.** Fetches a vendor URL, validates the document against the right spec (sniffed from `*_version`), hashes it canonically, and tracks **drift** across re-checks. The fourth layer of the AEO Reference Stack — what the CLI is, but always running, with history.
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
1. SDKs aeo-sdk-python / -typescript / -rust / -go / -swift
|
|
48
|
+
2. CLI aeo-cli
|
|
49
|
+
3. Crawler aeo-crawler
|
|
50
|
+
4. Validator service <- this repo
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Why a service instead of just the CLI
|
|
56
|
+
|
|
57
|
+
The CLI answers "is this doc valid right now." That's enough on a developer laptop. In production you want three more things:
|
|
58
|
+
|
|
59
|
+
1. **HTTP for non-Python services.** The CLI is Python-only. The service is a curl away.
|
|
60
|
+
2. **Drift over time.** Hash a vendor's AEO doc today, hash it again tomorrow, and tell me what changed. Not just "different" — *which field* changed. That's the signal that something's worth a Slack ping.
|
|
61
|
+
3. **Watches.** "Check this URL every hour and let me know when it goes invalid or its spec changes." The service holds the history so the diff has somewhere to anchor.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Install
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install aeo-validator-service
|
|
69
|
+
aeo-validator-service # binds 0.0.0.0:8091
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Python 3.11+. Runtime deps: `fastapi`, `httpx`, `pydantic`, `uvicorn`.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Endpoints
|
|
77
|
+
|
|
78
|
+
| Method | Path | What it does |
|
|
79
|
+
| --- | --- | --- |
|
|
80
|
+
| GET | `/` | Service info + supported spec list. |
|
|
81
|
+
| GET | `/healthz` | Liveness probe. |
|
|
82
|
+
| POST | `/validate/by-url` | Fetch + validate by URL. One-shot, no watch. |
|
|
83
|
+
| POST | `/validate/inline` | Validate an already-fetched document — no network. |
|
|
84
|
+
| POST | `/watches` | Create a persistent watch for a URL; the initial fetch + validation runs synchronously. |
|
|
85
|
+
| GET | `/watches` | List watch IDs. |
|
|
86
|
+
| GET | `/watches/{id}` | Watch metadata + last result. |
|
|
87
|
+
| GET | `/watches/{id}/history` | Full validation history (oldest → newest). |
|
|
88
|
+
| POST | `/watches/{id}/recheck` | Re-fetch + validate. Returns a structured **DriftReport** vs. the previous result. |
|
|
89
|
+
| DELETE | `/watches/{id}` | Delete the watch. |
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Supported specs
|
|
94
|
+
|
|
95
|
+
The validator sniffs the spec kind from the top-level `*_version` field — the same trick the [unified visualizer](https://github.com/mizcausevic-dev/kinetic-gain-visualizer) uses. Eleven specs auto-detected:
|
|
96
|
+
|
|
97
|
+
| Spec | Detected via |
|
|
98
|
+
| --- | --- |
|
|
99
|
+
| AEO Protocol | `aeo_version` |
|
|
100
|
+
| Prompt Provenance | `provenance_version` |
|
|
101
|
+
| Agent Cards | `agent_card_version` |
|
|
102
|
+
| AI Evidence Format | `evidence_version` |
|
|
103
|
+
| MCP Tool Cards | `tool_card_version` |
|
|
104
|
+
| AI Tutor Cards | `tutor_card_version` |
|
|
105
|
+
| Student AI Disclosure | `disclosure_version` |
|
|
106
|
+
| Classroom AI AUP | `aup_version` |
|
|
107
|
+
| Clinical AI Disclosure | `clinical_ai_card_version` |
|
|
108
|
+
| AI Incident Card | `incident_card_version` |
|
|
109
|
+
| AI Procurement Decision Card | `decision_card_version` |
|
|
110
|
+
|
|
111
|
+
For each one the validator runs:
|
|
112
|
+
|
|
113
|
+
- **Universal checks** — version field present, non-blank
|
|
114
|
+
- **Spec-specific smoke checks** — AEO entity has `id` + `type` + `name`; agent-card has `agent_id` + `capabilities`; decision-card with `approved-with-conditions` requires non-empty `conditions[]`; etc.
|
|
115
|
+
|
|
116
|
+
This isn't a full Schema validator — punt to the SDKs when every-field-typed validation is needed. The point of *this* layer is "does it look right at a glance" plus drift tracking.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Drift report
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"url": "https://acme.example/.well-known/aeo.json",
|
|
125
|
+
"drifted": true,
|
|
126
|
+
"spec_changed": false,
|
|
127
|
+
"became_invalid": false,
|
|
128
|
+
"became_valid": false,
|
|
129
|
+
"content_hash_before": "sha256:9a3f...",
|
|
130
|
+
"content_hash_after": "sha256:b7d1...",
|
|
131
|
+
"added_fields": ["claims"],
|
|
132
|
+
"removed_fields": [],
|
|
133
|
+
"changed_fields": ["entity"],
|
|
134
|
+
"before_issues": 0,
|
|
135
|
+
"after_issues": 0
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
A drift is *any* of: hash changed, spec kind changed, validity flipped, or top-level field set changed. Webhooks-on-drift are an obvious follow-up (PR welcome).
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Quick start
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# One-shot validation:
|
|
147
|
+
curl -X POST http://localhost:8091/validate/by-url \
|
|
148
|
+
-H 'Content-Type: application/json' \
|
|
149
|
+
-d '{"url": "https://acme.example/.well-known/aeo.json", "include_body": true}'
|
|
150
|
+
|
|
151
|
+
# Persistent watch:
|
|
152
|
+
curl -X POST http://localhost:8091/watches \
|
|
153
|
+
-H 'Content-Type: application/json' \
|
|
154
|
+
-d '{"url": "https://acme.example/.well-known/aeo.json"}'
|
|
155
|
+
# -> {"watch_id": "a1b2c3", ...}
|
|
156
|
+
|
|
157
|
+
# Some time later — re-check and see what changed:
|
|
158
|
+
curl -X POST http://localhost:8091/watches/a1b2c3/recheck
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Hashing convention
|
|
164
|
+
|
|
165
|
+
`content_hash` is `sha256:<hex>` over canonical JSON — sorted keys, no whitespace, UTF-8. Same convention as [`procurement-decision-api`](https://github.com/mizcausevic-dev/procurement-decision-api), so the two services produce **identical** `content_hash` values for identical documents.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Tests
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
pip install -e ".[dev]"
|
|
173
|
+
ruff check src tests && ruff format --check src tests
|
|
174
|
+
mypy src
|
|
175
|
+
pytest -v
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Test fixtures use `httpx.MockTransport` so nothing touches the network. CI matrix Python 3.11 / 3.12 / 3.13.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Related in this ecosystem
|
|
183
|
+
|
|
184
|
+
- **[aeo-protocol-spec](https://github.com/mizcausevic-dev/aeo-protocol-spec)** — the spec this service validates.
|
|
185
|
+
- **[aeo-cli](https://github.com/mizcausevic-dev/aeo-cli)** · **[aeo-crawler](https://github.com/mizcausevic-dev/aeo-crawler)** — layers 2 and 3 of the AEO Reference Stack.
|
|
186
|
+
- **[procurement-decision-api](https://github.com/mizcausevic-dev/procurement-decision-api)** — uses the same canonical-hash convention; the two pair naturally.
|
|
187
|
+
- More at [kineticgain.com](https://kineticgain.com/).
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# aeo-validator-service
|
|
2
|
+
|
|
3
|
+
[](https://github.com/mizcausevic-dev/aeo-validator-service/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.python.org/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
**Always-on validator service for AEO and the rest of the Kinetic Gain Protocol Suite.** Fetches a vendor URL, validates the document against the right spec (sniffed from `*_version`), hashes it canonically, and tracks **drift** across re-checks. The fourth layer of the AEO Reference Stack — what the CLI is, but always running, with history.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
1. SDKs aeo-sdk-python / -typescript / -rust / -go / -swift
|
|
11
|
+
2. CLI aeo-cli
|
|
12
|
+
3. Crawler aeo-crawler
|
|
13
|
+
4. Validator service <- this repo
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Why a service instead of just the CLI
|
|
19
|
+
|
|
20
|
+
The CLI answers "is this doc valid right now." That's enough on a developer laptop. In production you want three more things:
|
|
21
|
+
|
|
22
|
+
1. **HTTP for non-Python services.** The CLI is Python-only. The service is a curl away.
|
|
23
|
+
2. **Drift over time.** Hash a vendor's AEO doc today, hash it again tomorrow, and tell me what changed. Not just "different" — *which field* changed. That's the signal that something's worth a Slack ping.
|
|
24
|
+
3. **Watches.** "Check this URL every hour and let me know when it goes invalid or its spec changes." The service holds the history so the diff has somewhere to anchor.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install aeo-validator-service
|
|
32
|
+
aeo-validator-service # binds 0.0.0.0:8091
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Python 3.11+. Runtime deps: `fastapi`, `httpx`, `pydantic`, `uvicorn`.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Endpoints
|
|
40
|
+
|
|
41
|
+
| Method | Path | What it does |
|
|
42
|
+
| --- | --- | --- |
|
|
43
|
+
| GET | `/` | Service info + supported spec list. |
|
|
44
|
+
| GET | `/healthz` | Liveness probe. |
|
|
45
|
+
| POST | `/validate/by-url` | Fetch + validate by URL. One-shot, no watch. |
|
|
46
|
+
| POST | `/validate/inline` | Validate an already-fetched document — no network. |
|
|
47
|
+
| POST | `/watches` | Create a persistent watch for a URL; the initial fetch + validation runs synchronously. |
|
|
48
|
+
| GET | `/watches` | List watch IDs. |
|
|
49
|
+
| GET | `/watches/{id}` | Watch metadata + last result. |
|
|
50
|
+
| GET | `/watches/{id}/history` | Full validation history (oldest → newest). |
|
|
51
|
+
| POST | `/watches/{id}/recheck` | Re-fetch + validate. Returns a structured **DriftReport** vs. the previous result. |
|
|
52
|
+
| DELETE | `/watches/{id}` | Delete the watch. |
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Supported specs
|
|
57
|
+
|
|
58
|
+
The validator sniffs the spec kind from the top-level `*_version` field — the same trick the [unified visualizer](https://github.com/mizcausevic-dev/kinetic-gain-visualizer) uses. Eleven specs auto-detected:
|
|
59
|
+
|
|
60
|
+
| Spec | Detected via |
|
|
61
|
+
| --- | --- |
|
|
62
|
+
| AEO Protocol | `aeo_version` |
|
|
63
|
+
| Prompt Provenance | `provenance_version` |
|
|
64
|
+
| Agent Cards | `agent_card_version` |
|
|
65
|
+
| AI Evidence Format | `evidence_version` |
|
|
66
|
+
| MCP Tool Cards | `tool_card_version` |
|
|
67
|
+
| AI Tutor Cards | `tutor_card_version` |
|
|
68
|
+
| Student AI Disclosure | `disclosure_version` |
|
|
69
|
+
| Classroom AI AUP | `aup_version` |
|
|
70
|
+
| Clinical AI Disclosure | `clinical_ai_card_version` |
|
|
71
|
+
| AI Incident Card | `incident_card_version` |
|
|
72
|
+
| AI Procurement Decision Card | `decision_card_version` |
|
|
73
|
+
|
|
74
|
+
For each one the validator runs:
|
|
75
|
+
|
|
76
|
+
- **Universal checks** — version field present, non-blank
|
|
77
|
+
- **Spec-specific smoke checks** — AEO entity has `id` + `type` + `name`; agent-card has `agent_id` + `capabilities`; decision-card with `approved-with-conditions` requires non-empty `conditions[]`; etc.
|
|
78
|
+
|
|
79
|
+
This isn't a full Schema validator — punt to the SDKs when every-field-typed validation is needed. The point of *this* layer is "does it look right at a glance" plus drift tracking.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Drift report
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"url": "https://acme.example/.well-known/aeo.json",
|
|
88
|
+
"drifted": true,
|
|
89
|
+
"spec_changed": false,
|
|
90
|
+
"became_invalid": false,
|
|
91
|
+
"became_valid": false,
|
|
92
|
+
"content_hash_before": "sha256:9a3f...",
|
|
93
|
+
"content_hash_after": "sha256:b7d1...",
|
|
94
|
+
"added_fields": ["claims"],
|
|
95
|
+
"removed_fields": [],
|
|
96
|
+
"changed_fields": ["entity"],
|
|
97
|
+
"before_issues": 0,
|
|
98
|
+
"after_issues": 0
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
A drift is *any* of: hash changed, spec kind changed, validity flipped, or top-level field set changed. Webhooks-on-drift are an obvious follow-up (PR welcome).
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Quick start
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# One-shot validation:
|
|
110
|
+
curl -X POST http://localhost:8091/validate/by-url \
|
|
111
|
+
-H 'Content-Type: application/json' \
|
|
112
|
+
-d '{"url": "https://acme.example/.well-known/aeo.json", "include_body": true}'
|
|
113
|
+
|
|
114
|
+
# Persistent watch:
|
|
115
|
+
curl -X POST http://localhost:8091/watches \
|
|
116
|
+
-H 'Content-Type: application/json' \
|
|
117
|
+
-d '{"url": "https://acme.example/.well-known/aeo.json"}'
|
|
118
|
+
# -> {"watch_id": "a1b2c3", ...}
|
|
119
|
+
|
|
120
|
+
# Some time later — re-check and see what changed:
|
|
121
|
+
curl -X POST http://localhost:8091/watches/a1b2c3/recheck
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Hashing convention
|
|
127
|
+
|
|
128
|
+
`content_hash` is `sha256:<hex>` over canonical JSON — sorted keys, no whitespace, UTF-8. Same convention as [`procurement-decision-api`](https://github.com/mizcausevic-dev/procurement-decision-api), so the two services produce **identical** `content_hash` values for identical documents.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Tests
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
pip install -e ".[dev]"
|
|
136
|
+
ruff check src tests && ruff format --check src tests
|
|
137
|
+
mypy src
|
|
138
|
+
pytest -v
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Test fixtures use `httpx.MockTransport` so nothing touches the network. CI matrix Python 3.11 / 3.12 / 3.13.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Related in this ecosystem
|
|
146
|
+
|
|
147
|
+
- **[aeo-protocol-spec](https://github.com/mizcausevic-dev/aeo-protocol-spec)** — the spec this service validates.
|
|
148
|
+
- **[aeo-cli](https://github.com/mizcausevic-dev/aeo-cli)** · **[aeo-crawler](https://github.com/mizcausevic-dev/aeo-crawler)** — layers 2 and 3 of the AEO Reference Stack.
|
|
149
|
+
- **[procurement-decision-api](https://github.com/mizcausevic-dev/procurement-decision-api)** — uses the same canonical-hash convention; the two pair naturally.
|
|
150
|
+
- More at [kineticgain.com](https://kineticgain.com/).
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.25"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "aeo-validator-service"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Always-on validator service for AEO + Kinetic Gain Protocol Suite documents. Validate by URL, track content-hash drift, schedule re-fetch, emit structured diffs. The fourth layer of the AEO Reference Stack. Optional audit-stream-py integration via AUDIT_STREAM_URL env var."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Miz Causevic", email = "miz@kineticgain.com" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["aeo", "validator", "drift", "fastapi", "kinetic-gain-protocol-suite"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Framework :: FastAPI",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Topic :: Software Development :: Libraries",
|
|
27
|
+
"Topic :: System :: Monitoring",
|
|
28
|
+
"Typing :: Typed",
|
|
29
|
+
]
|
|
30
|
+
dependencies = [
|
|
31
|
+
"fastapi>=0.115",
|
|
32
|
+
"httpx>=0.27",
|
|
33
|
+
"pydantic>=2.7",
|
|
34
|
+
"uvicorn[standard]>=0.30",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
dev = [
|
|
39
|
+
"pytest>=8.2",
|
|
40
|
+
"pytest-asyncio>=0.23",
|
|
41
|
+
"ruff>=0.6",
|
|
42
|
+
"mypy>=1.11",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[project.scripts]
|
|
46
|
+
aeo-validator-service = "aeo_validator_service.__main__:main"
|
|
47
|
+
|
|
48
|
+
[project.urls]
|
|
49
|
+
Homepage = "https://github.com/mizcausevic-dev/aeo-validator-service"
|
|
50
|
+
Repository = "https://github.com/mizcausevic-dev/aeo-validator-service"
|
|
51
|
+
Issues = "https://github.com/mizcausevic-dev/aeo-validator-service/issues"
|
|
52
|
+
"AEO Spec" = "https://github.com/mizcausevic-dev/aeo-protocol-spec"
|
|
53
|
+
"Author Site" = "https://kineticgain.com/"
|
|
54
|
+
|
|
55
|
+
[tool.hatch.build.targets.wheel]
|
|
56
|
+
packages = ["src/aeo_validator_service"]
|
|
57
|
+
|
|
58
|
+
[tool.pytest.ini_options]
|
|
59
|
+
testpaths = ["tests"]
|
|
60
|
+
asyncio_mode = "auto"
|
|
61
|
+
filterwarnings = [
|
|
62
|
+
"ignore::DeprecationWarning:starlette.*",
|
|
63
|
+
"ignore::DeprecationWarning:fastapi.*",
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
[tool.ruff]
|
|
67
|
+
line-length = 110
|
|
68
|
+
target-version = "py311"
|
|
69
|
+
|
|
70
|
+
[tool.ruff.lint]
|
|
71
|
+
select = ["E", "F", "I", "B", "UP", "RUF"]
|
|
72
|
+
ignore = ["E501"]
|
|
73
|
+
|
|
74
|
+
[tool.mypy]
|
|
75
|
+
python_version = "3.11"
|
|
76
|
+
strict = true
|
|
77
|
+
disallow_untyped_defs = true
|
|
78
|
+
warn_unused_ignores = true
|
|
79
|
+
no_implicit_optional = true
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
aeo-validator-service — always-on AEO + Kinetic Gain Protocol Suite validator.
|
|
3
|
+
|
|
4
|
+
The fourth layer of the AEO Reference Stack:
|
|
5
|
+
|
|
6
|
+
1. SDKs (aeo-sdk-python / -typescript / -rust / -go / -swift)
|
|
7
|
+
2. CLI (aeo-cli)
|
|
8
|
+
3. Crawler (aeo-crawler)
|
|
9
|
+
-> 4. Validator service (this repo) — fetches a vendor URL, validates the
|
|
10
|
+
document, hashes it canonically, and tracks drift across check-ins.
|
|
11
|
+
|
|
12
|
+
What the CLI doesn't give you that this service does:
|
|
13
|
+
|
|
14
|
+
- HTTP API for non-Python callers
|
|
15
|
+
- Persistent per-URL history of content_hash + validation_result
|
|
16
|
+
- Drift detection: "did this vendor's AEO change since the last check?"
|
|
17
|
+
- Diff output that points at the field-level change
|
|
18
|
+
- Scheduled re-validation (POST /watches, then GET /watches/{id})
|
|
19
|
+
|
|
20
|
+
The service knows how to validate every spec in the Suite by sniffing the
|
|
21
|
+
top-level `*_version` field, the same trick the unified visualizer uses.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from .models import (
|
|
27
|
+
DriftReport,
|
|
28
|
+
SpecKind,
|
|
29
|
+
ValidationIssue,
|
|
30
|
+
ValidationResult,
|
|
31
|
+
Watch,
|
|
32
|
+
)
|
|
33
|
+
from .validator import SuiteValidator
|
|
34
|
+
|
|
35
|
+
__version__ = "0.1.1"
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"DriftReport",
|
|
39
|
+
"SpecKind",
|
|
40
|
+
"SuiteValidator",
|
|
41
|
+
"ValidationIssue",
|
|
42
|
+
"ValidationResult",
|
|
43
|
+
"Watch",
|
|
44
|
+
"__version__",
|
|
45
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Run the API. `python -m aeo_validator_service` or the installed script."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main() -> None:
|
|
9
|
+
import uvicorn
|
|
10
|
+
|
|
11
|
+
port = int(os.environ.get("PORT", "8091"))
|
|
12
|
+
host = os.environ.get("HOST", "0.0.0.0")
|
|
13
|
+
uvicorn.run("aeo_validator_service.app:app", host=host, port=port, log_level="info")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
if __name__ == "__main__":
|
|
17
|
+
main()
|