weir-scan 0.1.0__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.
- weir_scan-0.1.0/.gitignore +10 -0
- weir_scan-0.1.0/PKG-INFO +131 -0
- weir_scan-0.1.0/README.md +108 -0
- weir_scan-0.1.0/pyproject.toml +38 -0
- weir_scan-0.1.0/schema_artifacts/canonical_trace.v1.schema.json +289 -0
- weir_scan-0.1.0/src/weir/__init__.py +0 -0
- weir_scan-0.1.0/src/weir/adapters/__init__.py +5 -0
- weir_scan-0.1.0/src/weir/adapters/otel/__init__.py +17 -0
- weir_scan-0.1.0/src/weir/adapters/otel/_contract.py +297 -0
- weir_scan-0.1.0/src/weir/adapters/otel/_map.py +584 -0
- weir_scan-0.1.0/src/weir/adapters/otel/_wire.py +240 -0
- weir_scan-0.1.0/src/weir/catalog/__init__.py +16 -0
- weir_scan-0.1.0/src/weir/catalog/_types.py +50 -0
- weir_scan-0.1.0/src/weir/catalog/default.py +47 -0
- weir_scan-0.1.0/src/weir/catalog/digest.py +45 -0
- weir_scan-0.1.0/src/weir/catalog/eligibility.py +19 -0
- weir_scan-0.1.0/src/weir/catalog/structure_classes.py +43 -0
- weir_scan-0.1.0/src/weir/cli/__init__.py +0 -0
- weir_scan-0.1.0/src/weir/cli/main.py +230 -0
- weir_scan-0.1.0/src/weir/data/__init__.py +1 -0
- weir_scan-0.1.0/src/weir/data/sample-export.json +279 -0
- weir_scan-0.1.0/src/weir/evaluate/__init__.py +11 -0
- weir_scan-0.1.0/src/weir/evaluate/_types.py +25 -0
- weir_scan-0.1.0/src/weir/evaluate/evaluator.py +83 -0
- weir_scan-0.1.0/src/weir/evaluate/witness.py +46 -0
- weir_scan-0.1.0/src/weir/gauge/__init__.py +10 -0
- weir_scan-0.1.0/src/weir/gauge/_types.py +25 -0
- weir_scan-0.1.0/src/weir/gauge/gauge.py +103 -0
- weir_scan-0.1.0/src/weir/gauge/ladder.py +46 -0
- weir_scan-0.1.0/src/weir/graph/__init__.py +4 -0
- weir_scan-0.1.0/src/weir/graph/_types.py +27 -0
- weir_scan-0.1.0/src/weir/graph/builder.py +49 -0
- weir_scan-0.1.0/src/weir/label/__init__.py +4 -0
- weir_scan-0.1.0/src/weir/label/_types.py +26 -0
- weir_scan-0.1.0/src/weir/label/labeler.py +80 -0
- weir_scan-0.1.0/src/weir/py.typed +0 -0
- weir_scan-0.1.0/src/weir/report/__init__.py +11 -0
- weir_scan-0.1.0/src/weir/report/_template.py +86 -0
- weir_scan-0.1.0/src/weir/report/lexicon.py +20 -0
- weir_scan-0.1.0/src/weir/report/renderer.py +102 -0
- weir_scan-0.1.0/src/weir/report/text.py +43 -0
- weir_scan-0.1.0/src/weir/rules_commons/__init__.py +4 -0
- weir_scan-0.1.0/src/weir/rules_commons/_types.py +17 -0
- weir_scan-0.1.0/src/weir/rules_commons/bundled/injection-exfil-to-outbound-sink.json +9 -0
- weir_scan-0.1.0/src/weir/rules_commons/loader.py +28 -0
- weir_scan-0.1.0/src/weir/schema/__init__.py +0 -0
- weir_scan-0.1.0/src/weir/schema/_digest.py +75 -0
- weir_scan-0.1.0/src/weir/schema/baseline.py +208 -0
- weir_scan-0.1.0/src/weir/schema/delta.py +168 -0
- weir_scan-0.1.0/src/weir/schema/dialect.py +103 -0
- weir_scan-0.1.0/src/weir/schema/flowfact.py +179 -0
- weir_scan-0.1.0/src/weir/schema/trace.py +143 -0
- weir_scan-0.1.0/src/weir/taint/__init__.py +12 -0
- weir_scan-0.1.0/src/weir/taint/_types.py +24 -0
- weir_scan-0.1.0/src/weir/taint/canon.py +9 -0
- weir_scan-0.1.0/src/weir/taint/engine.py +99 -0
- weir_scan-0.1.0/src/weir/taint/reachability.py +33 -0
- weir_scan-0.1.0/tests/test_adapter_contract.py +44 -0
- weir_scan-0.1.0/tests/test_adapter_map.py +204 -0
- weir_scan-0.1.0/tests/test_adapter_wire.py +177 -0
- weir_scan-0.1.0/tests/test_baseline.py +274 -0
- weir_scan-0.1.0/tests/test_catalog.py +64 -0
- weir_scan-0.1.0/tests/test_catalog_digest.py +119 -0
- weir_scan-0.1.0/tests/test_cli.py +221 -0
- weir_scan-0.1.0/tests/test_contract_doc_drift.py +15 -0
- weir_scan-0.1.0/tests/test_delta.py +315 -0
- weir_scan-0.1.0/tests/test_dialect.py +101 -0
- weir_scan-0.1.0/tests/test_evaluate.py +221 -0
- weir_scan-0.1.0/tests/test_flowfact.py +263 -0
- weir_scan-0.1.0/tests/test_gauge.py +173 -0
- weir_scan-0.1.0/tests/test_gauge_ladder.py +37 -0
- weir_scan-0.1.0/tests/test_graph_builder.py +76 -0
- weir_scan-0.1.0/tests/test_labeler.py +87 -0
- weir_scan-0.1.0/tests/test_packaged_readme_drift.py +14 -0
- weir_scan-0.1.0/tests/test_readme_truth.py +55 -0
- weir_scan-0.1.0/tests/test_remediation_sources.py +42 -0
- weir_scan-0.1.0/tests/test_report.py +125 -0
- weir_scan-0.1.0/tests/test_rule_loader.py +58 -0
- weir_scan-0.1.0/tests/test_sample_data_drift.py +19 -0
- weir_scan-0.1.0/tests/test_schema_digest.py +96 -0
- weir_scan-0.1.0/tests/test_taint_engine.py +145 -0
- weir_scan-0.1.0/tests/test_trace_schema.py +132 -0
weir_scan-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: weir-scan
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: weir - deterministic, post-hoc verification engine for AI-agent sessions
|
|
5
|
+
Project-URL: Homepage, https://github.com/IdoGol24/weir
|
|
6
|
+
Project-URL: Repository, https://github.com/IdoGol24/weir
|
|
7
|
+
Project-URL: Issues, https://github.com/IdoGol24/weir/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/IdoGol24/weir/blob/main/weir/CHANGELOG.md
|
|
9
|
+
Project-URL: Security, https://github.com/IdoGol24/weir/blob/main/weir/SECURITY.md
|
|
10
|
+
License-Expression: Apache-2.0
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Security
|
|
16
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Requires-Dist: click>=8.1
|
|
20
|
+
Requires-Dist: jinja2>=3.1
|
|
21
|
+
Requires-Dist: msgspec>=0.18
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# Weir - unit tests for your agents
|
|
25
|
+
|
|
26
|
+
[](https://github.com/IdoGol24/weir/actions/workflows/ci.yml) [](https://pypi.org/project/weir-scan/) [](LICENSE)
|
|
27
|
+
|
|
28
|
+
<p align="center">
|
|
29
|
+
<img src="docs/assets/weir.jpg" alt="A weir: a low dam across a river, with water flowing evenly over its crest" width="720">
|
|
30
|
+
<br>
|
|
31
|
+
<em>A weir is a low dam built across a river to regulate and measure its
|
|
32
|
+
flow - the water keeps moving; the measurement happens anyway.</em>
|
|
33
|
+
<br>
|
|
34
|
+
<sub>Damhead Weir, Water of Leith. Photo by 501ghost, Wikimedia Commons, CC0.</sub>
|
|
35
|
+
</p>
|
|
36
|
+
|
|
37
|
+
You cannot unit-test an agent by string-matching its output, and an LLM
|
|
38
|
+
judge drifts with its model. But what you actually need to assert is
|
|
39
|
+
structural: did untrusted tool output reach an outbound sink, did the
|
|
40
|
+
payment tool fire without its guard, did the secret leave the session.
|
|
41
|
+
Those facts live in the traces your agent already emits.
|
|
42
|
+
|
|
43
|
+
Weir is that assertion. Point it at an OpenTelemetry GenAI export; it
|
|
44
|
+
exits `1` if a forbidden flow happened - with a witness path you can walk
|
|
45
|
+
node by node - and `0` if not. Deterministic and byte-identical on every
|
|
46
|
+
run, no LLM in the loop, no network access, never runs your agent (all
|
|
47
|
+
tested guarantees). Like its namesake: the session keeps flowing; the
|
|
48
|
+
measurement happens anyway.
|
|
49
|
+
|
|
50
|
+
## Try it in two minutes
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
pip install weir-scan
|
|
54
|
+
weir gauge your-export.jsonl # or: weir gauge --sample
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
<!-- verify: gauge --sample -->
|
|
58
|
+
```
|
|
59
|
+
evidentiary coverage: 0%
|
|
60
|
+
argument capture: 0%
|
|
61
|
+
degraded: 100%
|
|
62
|
+
tool arguments not captured - this scope is emitted by Traceloop/OpenLLMetry's LangChain instrumentation, which captures content to span attributes by default; check TRACELOOP_TRACE_CONTENT (false disables capture) in the traced service's environment
|
|
63
|
+
linkage: explicit (gen_ai.tool.call.id present)
|
|
64
|
+
payloads: absent - content capture is off
|
|
65
|
+
at your current telemetry: coverage reporting YES - taint/scan NO
|
|
66
|
+
content capture is off; for OTel GenAI instrumentations built on the util-genai layer, set OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental and OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=SPAN_ONLY to capture gen_ai.input.messages / gen_ai.output.messages / gen_ai.tool.call.arguments and unlock cross-step analysis
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The gauge answers the question every other tool skips: can your telemetry
|
|
70
|
+
support the assertion you want to write? Most real exports cannot yet
|
|
71
|
+
(content capture is off by default across the ecosystem), so it names the
|
|
72
|
+
exact switch - derived from the instrumentation recorded in the trace
|
|
73
|
+
itself. Flip it, re-run, and `weir scan` becomes your unit test:
|
|
74
|
+
|
|
75
|
+
<!-- verify: scan fixtures/injection-exfil.json -->
|
|
76
|
+
```
|
|
77
|
+
1 verdict-grade finding(s)
|
|
78
|
+
finding: injection-exfil-to-outbound-sink
|
|
79
|
+
source: financial_account_identifier at node 2 (tool_result)
|
|
80
|
+
sink: send_email at node 6
|
|
81
|
+
witness path: n2 -> n3 -> n4 -> n5 -> n6
|
|
82
|
+
join tiers crossed: explicit
|
|
83
|
+
verdict grade: yes
|
|
84
|
+
matched value: 22 chars
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Exit `1` - fail the build. The finding carries its evidence, with the
|
|
88
|
+
matched secret redacted. Structural facts do not flap: rewording and
|
|
89
|
+
step-count variance cannot move them, and when evidence genuinely weakens
|
|
90
|
+
a finding demotes with its reason stated instead of silently flipping.
|
|
91
|
+
|
|
92
|
+
## Why you can trust it
|
|
93
|
+
|
|
94
|
+
- **It reads your traces, not ours.** The suite includes a frozen JSONL
|
|
95
|
+
capture produced by `opentelemetry-sdk` and Google's protojson encoder
|
|
96
|
+
that no weir code touched ([provenance](fixtures/foreign/PROVENANCE.md)).
|
|
97
|
+
Ingestion is reject-narrow: only not-telemetry is refused; every real
|
|
98
|
+
malformation degrades under one of 18 named contract rows with a
|
|
99
|
+
remediation, generated from code and drift-tested:
|
|
100
|
+
[docs/contract.md](docs/contract.md).
|
|
101
|
+
- **Attacker content cannot rewire it.** Joins follow evidence tiers
|
|
102
|
+
(explicit id, then span nesting, then content-mined); content-mined
|
|
103
|
+
evidence fills absences only, ambiguity is reported rather than
|
|
104
|
+
resolved, and a finding crossing a content-mined join is never
|
|
105
|
+
verdict-grade. If you can make attacker content do more than add
|
|
106
|
+
visible low-confidence noise, that is a security bug:
|
|
107
|
+
[SECURITY.md](SECURITY.md).
|
|
108
|
+
- **The gauge is calibrated against known ground truth.** A paired
|
|
109
|
+
generator emits each scenario as native traces and OTLP-JSON from one
|
|
110
|
+
plan; the adapter's acceptance test is byte-for-byte equivalence, and
|
|
111
|
+
gauge numbers are pinned against corpora with known degradation,
|
|
112
|
+
mutation-proven.
|
|
113
|
+
- **Every external claim is sourced.** Remediation strings that describe
|
|
114
|
+
someone else's software carry a recorded source and check date:
|
|
115
|
+
[REMEDIATION_SOURCES.md](REMEDIATION_SOURCES.md).
|
|
116
|
+
|
|
117
|
+
## What ships, what does not
|
|
118
|
+
|
|
119
|
+
Shipped: the OTel GenAI adapter (pinned `otel-genai/1.42.0`), session
|
|
120
|
+
graph, verbatim taint and evaluation, the gauge and its capability ladder,
|
|
121
|
+
HTML reports, the paired generator and corpora, one teaching rule
|
|
122
|
+
(injection-to-exfiltration). Roadmap: the `weir diff` baseline gate, the
|
|
123
|
+
rule-contribution gate and a broader rule set, signed bundles, more
|
|
124
|
+
dialect rows.
|
|
125
|
+
|
|
126
|
+
Open core, in writing: the engine - including the future diff gate - is
|
|
127
|
+
Apache-2.0 permanently; authenticity, never entitlement. Full signed
|
|
128
|
+
boundary: [docs/open-core.md](docs/open-core.md).
|
|
129
|
+
|
|
130
|
+
Install name `weir-scan` (PyPI `weir` was taken); import `weir`; command
|
|
131
|
+
`weir`. Apache-2.0.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Weir - unit tests for your agents
|
|
2
|
+
|
|
3
|
+
[](https://github.com/IdoGol24/weir/actions/workflows/ci.yml) [](https://pypi.org/project/weir-scan/) [](LICENSE)
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="docs/assets/weir.jpg" alt="A weir: a low dam across a river, with water flowing evenly over its crest" width="720">
|
|
7
|
+
<br>
|
|
8
|
+
<em>A weir is a low dam built across a river to regulate and measure its
|
|
9
|
+
flow - the water keeps moving; the measurement happens anyway.</em>
|
|
10
|
+
<br>
|
|
11
|
+
<sub>Damhead Weir, Water of Leith. Photo by 501ghost, Wikimedia Commons, CC0.</sub>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
You cannot unit-test an agent by string-matching its output, and an LLM
|
|
15
|
+
judge drifts with its model. But what you actually need to assert is
|
|
16
|
+
structural: did untrusted tool output reach an outbound sink, did the
|
|
17
|
+
payment tool fire without its guard, did the secret leave the session.
|
|
18
|
+
Those facts live in the traces your agent already emits.
|
|
19
|
+
|
|
20
|
+
Weir is that assertion. Point it at an OpenTelemetry GenAI export; it
|
|
21
|
+
exits `1` if a forbidden flow happened - with a witness path you can walk
|
|
22
|
+
node by node - and `0` if not. Deterministic and byte-identical on every
|
|
23
|
+
run, no LLM in the loop, no network access, never runs your agent (all
|
|
24
|
+
tested guarantees). Like its namesake: the session keeps flowing; the
|
|
25
|
+
measurement happens anyway.
|
|
26
|
+
|
|
27
|
+
## Try it in two minutes
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
pip install weir-scan
|
|
31
|
+
weir gauge your-export.jsonl # or: weir gauge --sample
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
<!-- verify: gauge --sample -->
|
|
35
|
+
```
|
|
36
|
+
evidentiary coverage: 0%
|
|
37
|
+
argument capture: 0%
|
|
38
|
+
degraded: 100%
|
|
39
|
+
tool arguments not captured - this scope is emitted by Traceloop/OpenLLMetry's LangChain instrumentation, which captures content to span attributes by default; check TRACELOOP_TRACE_CONTENT (false disables capture) in the traced service's environment
|
|
40
|
+
linkage: explicit (gen_ai.tool.call.id present)
|
|
41
|
+
payloads: absent - content capture is off
|
|
42
|
+
at your current telemetry: coverage reporting YES - taint/scan NO
|
|
43
|
+
content capture is off; for OTel GenAI instrumentations built on the util-genai layer, set OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental and OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=SPAN_ONLY to capture gen_ai.input.messages / gen_ai.output.messages / gen_ai.tool.call.arguments and unlock cross-step analysis
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The gauge answers the question every other tool skips: can your telemetry
|
|
47
|
+
support the assertion you want to write? Most real exports cannot yet
|
|
48
|
+
(content capture is off by default across the ecosystem), so it names the
|
|
49
|
+
exact switch - derived from the instrumentation recorded in the trace
|
|
50
|
+
itself. Flip it, re-run, and `weir scan` becomes your unit test:
|
|
51
|
+
|
|
52
|
+
<!-- verify: scan fixtures/injection-exfil.json -->
|
|
53
|
+
```
|
|
54
|
+
1 verdict-grade finding(s)
|
|
55
|
+
finding: injection-exfil-to-outbound-sink
|
|
56
|
+
source: financial_account_identifier at node 2 (tool_result)
|
|
57
|
+
sink: send_email at node 6
|
|
58
|
+
witness path: n2 -> n3 -> n4 -> n5 -> n6
|
|
59
|
+
join tiers crossed: explicit
|
|
60
|
+
verdict grade: yes
|
|
61
|
+
matched value: 22 chars
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Exit `1` - fail the build. The finding carries its evidence, with the
|
|
65
|
+
matched secret redacted. Structural facts do not flap: rewording and
|
|
66
|
+
step-count variance cannot move them, and when evidence genuinely weakens
|
|
67
|
+
a finding demotes with its reason stated instead of silently flipping.
|
|
68
|
+
|
|
69
|
+
## Why you can trust it
|
|
70
|
+
|
|
71
|
+
- **It reads your traces, not ours.** The suite includes a frozen JSONL
|
|
72
|
+
capture produced by `opentelemetry-sdk` and Google's protojson encoder
|
|
73
|
+
that no weir code touched ([provenance](fixtures/foreign/PROVENANCE.md)).
|
|
74
|
+
Ingestion is reject-narrow: only not-telemetry is refused; every real
|
|
75
|
+
malformation degrades under one of 18 named contract rows with a
|
|
76
|
+
remediation, generated from code and drift-tested:
|
|
77
|
+
[docs/contract.md](docs/contract.md).
|
|
78
|
+
- **Attacker content cannot rewire it.** Joins follow evidence tiers
|
|
79
|
+
(explicit id, then span nesting, then content-mined); content-mined
|
|
80
|
+
evidence fills absences only, ambiguity is reported rather than
|
|
81
|
+
resolved, and a finding crossing a content-mined join is never
|
|
82
|
+
verdict-grade. If you can make attacker content do more than add
|
|
83
|
+
visible low-confidence noise, that is a security bug:
|
|
84
|
+
[SECURITY.md](SECURITY.md).
|
|
85
|
+
- **The gauge is calibrated against known ground truth.** A paired
|
|
86
|
+
generator emits each scenario as native traces and OTLP-JSON from one
|
|
87
|
+
plan; the adapter's acceptance test is byte-for-byte equivalence, and
|
|
88
|
+
gauge numbers are pinned against corpora with known degradation,
|
|
89
|
+
mutation-proven.
|
|
90
|
+
- **Every external claim is sourced.** Remediation strings that describe
|
|
91
|
+
someone else's software carry a recorded source and check date:
|
|
92
|
+
[REMEDIATION_SOURCES.md](REMEDIATION_SOURCES.md).
|
|
93
|
+
|
|
94
|
+
## What ships, what does not
|
|
95
|
+
|
|
96
|
+
Shipped: the OTel GenAI adapter (pinned `otel-genai/1.42.0`), session
|
|
97
|
+
graph, verbatim taint and evaluation, the gauge and its capability ladder,
|
|
98
|
+
HTML reports, the paired generator and corpora, one teaching rule
|
|
99
|
+
(injection-to-exfiltration). Roadmap: the `weir diff` baseline gate, the
|
|
100
|
+
rule-contribution gate and a broader rule set, signed bundles, more
|
|
101
|
+
dialect rows.
|
|
102
|
+
|
|
103
|
+
Open core, in writing: the engine - including the future diff gate - is
|
|
104
|
+
Apache-2.0 permanently; authenticity, never entitlement. Full signed
|
|
105
|
+
boundary: [docs/open-core.md](docs/open-core.md).
|
|
106
|
+
|
|
107
|
+
Install name `weir-scan` (PyPI `weir` was taken); import `weir`; command
|
|
108
|
+
`weir`. Apache-2.0.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "weir-scan"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "weir - deterministic, post-hoc verification engine for AI-agent sessions"
|
|
5
|
+
license = "Apache-2.0"
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
requires-python = ">=3.12"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"msgspec>=0.18",
|
|
10
|
+
"click>=8.1",
|
|
11
|
+
"jinja2>=3.1",
|
|
12
|
+
]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"License :: OSI Approved :: Apache Software License",
|
|
16
|
+
"Programming Language :: Python :: 3.12",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
19
|
+
"Topic :: Security",
|
|
20
|
+
"Typing :: Typed",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[build-system]
|
|
24
|
+
requires = ["hatchling"]
|
|
25
|
+
build-backend = "hatchling.build"
|
|
26
|
+
|
|
27
|
+
[tool.hatch.build.targets.wheel]
|
|
28
|
+
packages = ["src/weir"]
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
weir = "weir.cli.main:main"
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://github.com/IdoGol24/weir"
|
|
35
|
+
Repository = "https://github.com/IdoGol24/weir"
|
|
36
|
+
Issues = "https://github.com/IdoGol24/weir/issues"
|
|
37
|
+
Changelog = "https://github.com/IdoGol24/weir/blob/main/weir/CHANGELOG.md"
|
|
38
|
+
Security = "https://github.com/IdoGol24/weir/blob/main/weir/SECURITY.md"
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$defs": {
|
|
3
|
+
"CanonicalTrace": {
|
|
4
|
+
"additionalProperties": false,
|
|
5
|
+
"properties": {
|
|
6
|
+
"joins": {
|
|
7
|
+
"items": {
|
|
8
|
+
"$ref": "#/$defs/JoinRecord"
|
|
9
|
+
},
|
|
10
|
+
"type": "array"
|
|
11
|
+
},
|
|
12
|
+
"metadata": {
|
|
13
|
+
"$ref": "#/$defs/TraceMetadata"
|
|
14
|
+
},
|
|
15
|
+
"nodes": {
|
|
16
|
+
"items": {
|
|
17
|
+
"$ref": "#/$defs/TraceNode"
|
|
18
|
+
},
|
|
19
|
+
"type": "array"
|
|
20
|
+
},
|
|
21
|
+
"schema_version": {
|
|
22
|
+
"type": "string"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"required": [
|
|
26
|
+
"schema_version",
|
|
27
|
+
"nodes",
|
|
28
|
+
"joins",
|
|
29
|
+
"metadata"
|
|
30
|
+
],
|
|
31
|
+
"title": "CanonicalTrace",
|
|
32
|
+
"type": "object"
|
|
33
|
+
},
|
|
34
|
+
"JoinConfidence": {
|
|
35
|
+
"description": "Join precedence per R1.4, amended by the M4 design (section 3):\n explicit id > parent/child nesting > content-mined > heuristic.\n CONTENT_MINED never competes with the envelope tiers - it fills absences\n only, and any finding whose witness path traverses it is never\n verdict-grade. HEURISTIC is named by R1.4 but nothing assigns it in M4.",
|
|
36
|
+
"enum": [
|
|
37
|
+
"content_mined",
|
|
38
|
+
"explicit",
|
|
39
|
+
"heuristic",
|
|
40
|
+
"nested"
|
|
41
|
+
],
|
|
42
|
+
"title": "JoinConfidence"
|
|
43
|
+
},
|
|
44
|
+
"JoinRecord": {
|
|
45
|
+
"additionalProperties": false,
|
|
46
|
+
"description": "Ties a tool_call node to its tool_result node (R1.4). `join_confidence`\n is carried permanently - never discarded after joining - because it feeds\n the \u00a77 verdict-grade decision downstream. `join_source` is the MECHANISM\n detail (which attribute / which mined pattern, per dialect); it is\n adapter-determined provenance and sits in the M4 equivalence\n normalization set, so the enum never grows for mechanism reasons.",
|
|
47
|
+
"properties": {
|
|
48
|
+
"join_confidence": {
|
|
49
|
+
"$ref": "#/$defs/JoinConfidence"
|
|
50
|
+
},
|
|
51
|
+
"join_source": {
|
|
52
|
+
"anyOf": [
|
|
53
|
+
{
|
|
54
|
+
"type": "string"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"type": "null"
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"default": null
|
|
61
|
+
},
|
|
62
|
+
"tool_call_source_ref": {
|
|
63
|
+
"type": "string"
|
|
64
|
+
},
|
|
65
|
+
"tool_result_source_ref": {
|
|
66
|
+
"type": "string"
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"required": [
|
|
70
|
+
"tool_call_source_ref",
|
|
71
|
+
"tool_result_source_ref",
|
|
72
|
+
"join_confidence"
|
|
73
|
+
],
|
|
74
|
+
"title": "JoinRecord",
|
|
75
|
+
"type": "object"
|
|
76
|
+
},
|
|
77
|
+
"LlmCallPayload": {
|
|
78
|
+
"additionalProperties": false,
|
|
79
|
+
"properties": {
|
|
80
|
+
"content": {
|
|
81
|
+
"type": "string"
|
|
82
|
+
},
|
|
83
|
+
"type": {
|
|
84
|
+
"enum": [
|
|
85
|
+
"llm_call"
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"required": [
|
|
90
|
+
"type",
|
|
91
|
+
"content"
|
|
92
|
+
],
|
|
93
|
+
"title": "LlmCallPayload",
|
|
94
|
+
"type": "object"
|
|
95
|
+
},
|
|
96
|
+
"NodeKind": {
|
|
97
|
+
"enum": [
|
|
98
|
+
"llm_call",
|
|
99
|
+
"tool_call",
|
|
100
|
+
"tool_result",
|
|
101
|
+
"user_input"
|
|
102
|
+
],
|
|
103
|
+
"title": "NodeKind"
|
|
104
|
+
},
|
|
105
|
+
"ToolCallPayload": {
|
|
106
|
+
"additionalProperties": false,
|
|
107
|
+
"properties": {
|
|
108
|
+
"args": {
|
|
109
|
+
"additionalProperties": {
|
|
110
|
+
"type": [
|
|
111
|
+
"null",
|
|
112
|
+
"boolean",
|
|
113
|
+
"integer",
|
|
114
|
+
"number",
|
|
115
|
+
"string",
|
|
116
|
+
"array",
|
|
117
|
+
"object"
|
|
118
|
+
]
|
|
119
|
+
},
|
|
120
|
+
"type": "object"
|
|
121
|
+
},
|
|
122
|
+
"tool_name": {
|
|
123
|
+
"type": "string"
|
|
124
|
+
},
|
|
125
|
+
"type": {
|
|
126
|
+
"enum": [
|
|
127
|
+
"tool_call"
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"required": [
|
|
132
|
+
"type",
|
|
133
|
+
"tool_name",
|
|
134
|
+
"args"
|
|
135
|
+
],
|
|
136
|
+
"title": "ToolCallPayload",
|
|
137
|
+
"type": "object"
|
|
138
|
+
},
|
|
139
|
+
"ToolResultPayload": {
|
|
140
|
+
"additionalProperties": false,
|
|
141
|
+
"properties": {
|
|
142
|
+
"content": {
|
|
143
|
+
"type": "string"
|
|
144
|
+
},
|
|
145
|
+
"type": {
|
|
146
|
+
"enum": [
|
|
147
|
+
"tool_result"
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
"required": [
|
|
152
|
+
"type",
|
|
153
|
+
"content"
|
|
154
|
+
],
|
|
155
|
+
"title": "ToolResultPayload",
|
|
156
|
+
"type": "object"
|
|
157
|
+
},
|
|
158
|
+
"TraceMetadata": {
|
|
159
|
+
"additionalProperties": false,
|
|
160
|
+
"description": "R1.6: adapter + detected framework versions, so the Gauge can flag drift.",
|
|
161
|
+
"properties": {
|
|
162
|
+
"adapter_name": {
|
|
163
|
+
"type": "string"
|
|
164
|
+
},
|
|
165
|
+
"adapter_version": {
|
|
166
|
+
"type": "string"
|
|
167
|
+
},
|
|
168
|
+
"framework_name": {
|
|
169
|
+
"anyOf": [
|
|
170
|
+
{
|
|
171
|
+
"type": "string"
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"type": "null"
|
|
175
|
+
}
|
|
176
|
+
],
|
|
177
|
+
"default": null
|
|
178
|
+
},
|
|
179
|
+
"framework_version": {
|
|
180
|
+
"anyOf": [
|
|
181
|
+
{
|
|
182
|
+
"type": "string"
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"type": "null"
|
|
186
|
+
}
|
|
187
|
+
],
|
|
188
|
+
"default": null
|
|
189
|
+
},
|
|
190
|
+
"instrumentation_scope": {
|
|
191
|
+
"anyOf": [
|
|
192
|
+
{
|
|
193
|
+
"type": "string"
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"type": "null"
|
|
197
|
+
}
|
|
198
|
+
],
|
|
199
|
+
"default": null
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
"required": [
|
|
203
|
+
"adapter_name",
|
|
204
|
+
"adapter_version"
|
|
205
|
+
],
|
|
206
|
+
"title": "TraceMetadata",
|
|
207
|
+
"type": "object"
|
|
208
|
+
},
|
|
209
|
+
"TraceNode": {
|
|
210
|
+
"additionalProperties": false,
|
|
211
|
+
"properties": {
|
|
212
|
+
"actor": {
|
|
213
|
+
"type": "string"
|
|
214
|
+
},
|
|
215
|
+
"degraded": {
|
|
216
|
+
"default": false,
|
|
217
|
+
"type": "boolean"
|
|
218
|
+
},
|
|
219
|
+
"id": {
|
|
220
|
+
"type": "string"
|
|
221
|
+
},
|
|
222
|
+
"kind": {
|
|
223
|
+
"$ref": "#/$defs/NodeKind"
|
|
224
|
+
},
|
|
225
|
+
"payload": {
|
|
226
|
+
"anyOf": [
|
|
227
|
+
{
|
|
228
|
+
"$ref": "#/$defs/ToolCallPayload"
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"$ref": "#/$defs/ToolResultPayload"
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
"$ref": "#/$defs/LlmCallPayload"
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"$ref": "#/$defs/UserInputPayload"
|
|
238
|
+
}
|
|
239
|
+
],
|
|
240
|
+
"discriminator": {
|
|
241
|
+
"mapping": {
|
|
242
|
+
"llm_call": "#/$defs/LlmCallPayload",
|
|
243
|
+
"tool_call": "#/$defs/ToolCallPayload",
|
|
244
|
+
"tool_result": "#/$defs/ToolResultPayload",
|
|
245
|
+
"user_input": "#/$defs/UserInputPayload"
|
|
246
|
+
},
|
|
247
|
+
"propertyName": "type"
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
"source_ref": {
|
|
251
|
+
"type": "string"
|
|
252
|
+
},
|
|
253
|
+
"timestamp": {
|
|
254
|
+
"type": "string"
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
"required": [
|
|
258
|
+
"id",
|
|
259
|
+
"kind",
|
|
260
|
+
"timestamp",
|
|
261
|
+
"actor",
|
|
262
|
+
"source_ref",
|
|
263
|
+
"payload"
|
|
264
|
+
],
|
|
265
|
+
"title": "TraceNode",
|
|
266
|
+
"type": "object"
|
|
267
|
+
},
|
|
268
|
+
"UserInputPayload": {
|
|
269
|
+
"additionalProperties": false,
|
|
270
|
+
"properties": {
|
|
271
|
+
"content": {
|
|
272
|
+
"type": "string"
|
|
273
|
+
},
|
|
274
|
+
"type": {
|
|
275
|
+
"enum": [
|
|
276
|
+
"user_input"
|
|
277
|
+
]
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
"required": [
|
|
281
|
+
"type",
|
|
282
|
+
"content"
|
|
283
|
+
],
|
|
284
|
+
"title": "UserInputPayload",
|
|
285
|
+
"type": "object"
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
"$ref": "#/$defs/CanonicalTrace"
|
|
289
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""OTel GenAI OTLP-JSON adapter (M4): bytes -> CanonicalTrace + ledger.
|
|
2
|
+
|
|
3
|
+
Reject-narrow: OtlpRejectError (exit-2 material) fires only for
|
|
4
|
+
not-JSON / not-OTLP-shaped input. Everything else degrades, named."""
|
|
5
|
+
|
|
6
|
+
from weir.adapters.otel._contract import REMEDIATION, Degradation, DegradationReason
|
|
7
|
+
from weir.adapters.otel._map import ADAPTER_NAME, ADAPTER_VERSION, AdapterResult, map_wire
|
|
8
|
+
from weir.adapters.otel._wire import OtlpRejectError, decode_input
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"ADAPTER_NAME", "ADAPTER_VERSION", "AdapterResult", "Degradation",
|
|
12
|
+
"DegradationReason", "OtlpRejectError", "REMEDIATION", "adapt_otlp",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def adapt_otlp(data: bytes) -> AdapterResult:
|
|
17
|
+
return map_wire(decode_input(data))
|