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.
Files changed (82) hide show
  1. weir_scan-0.1.0/.gitignore +10 -0
  2. weir_scan-0.1.0/PKG-INFO +131 -0
  3. weir_scan-0.1.0/README.md +108 -0
  4. weir_scan-0.1.0/pyproject.toml +38 -0
  5. weir_scan-0.1.0/schema_artifacts/canonical_trace.v1.schema.json +289 -0
  6. weir_scan-0.1.0/src/weir/__init__.py +0 -0
  7. weir_scan-0.1.0/src/weir/adapters/__init__.py +5 -0
  8. weir_scan-0.1.0/src/weir/adapters/otel/__init__.py +17 -0
  9. weir_scan-0.1.0/src/weir/adapters/otel/_contract.py +297 -0
  10. weir_scan-0.1.0/src/weir/adapters/otel/_map.py +584 -0
  11. weir_scan-0.1.0/src/weir/adapters/otel/_wire.py +240 -0
  12. weir_scan-0.1.0/src/weir/catalog/__init__.py +16 -0
  13. weir_scan-0.1.0/src/weir/catalog/_types.py +50 -0
  14. weir_scan-0.1.0/src/weir/catalog/default.py +47 -0
  15. weir_scan-0.1.0/src/weir/catalog/digest.py +45 -0
  16. weir_scan-0.1.0/src/weir/catalog/eligibility.py +19 -0
  17. weir_scan-0.1.0/src/weir/catalog/structure_classes.py +43 -0
  18. weir_scan-0.1.0/src/weir/cli/__init__.py +0 -0
  19. weir_scan-0.1.0/src/weir/cli/main.py +230 -0
  20. weir_scan-0.1.0/src/weir/data/__init__.py +1 -0
  21. weir_scan-0.1.0/src/weir/data/sample-export.json +279 -0
  22. weir_scan-0.1.0/src/weir/evaluate/__init__.py +11 -0
  23. weir_scan-0.1.0/src/weir/evaluate/_types.py +25 -0
  24. weir_scan-0.1.0/src/weir/evaluate/evaluator.py +83 -0
  25. weir_scan-0.1.0/src/weir/evaluate/witness.py +46 -0
  26. weir_scan-0.1.0/src/weir/gauge/__init__.py +10 -0
  27. weir_scan-0.1.0/src/weir/gauge/_types.py +25 -0
  28. weir_scan-0.1.0/src/weir/gauge/gauge.py +103 -0
  29. weir_scan-0.1.0/src/weir/gauge/ladder.py +46 -0
  30. weir_scan-0.1.0/src/weir/graph/__init__.py +4 -0
  31. weir_scan-0.1.0/src/weir/graph/_types.py +27 -0
  32. weir_scan-0.1.0/src/weir/graph/builder.py +49 -0
  33. weir_scan-0.1.0/src/weir/label/__init__.py +4 -0
  34. weir_scan-0.1.0/src/weir/label/_types.py +26 -0
  35. weir_scan-0.1.0/src/weir/label/labeler.py +80 -0
  36. weir_scan-0.1.0/src/weir/py.typed +0 -0
  37. weir_scan-0.1.0/src/weir/report/__init__.py +11 -0
  38. weir_scan-0.1.0/src/weir/report/_template.py +86 -0
  39. weir_scan-0.1.0/src/weir/report/lexicon.py +20 -0
  40. weir_scan-0.1.0/src/weir/report/renderer.py +102 -0
  41. weir_scan-0.1.0/src/weir/report/text.py +43 -0
  42. weir_scan-0.1.0/src/weir/rules_commons/__init__.py +4 -0
  43. weir_scan-0.1.0/src/weir/rules_commons/_types.py +17 -0
  44. weir_scan-0.1.0/src/weir/rules_commons/bundled/injection-exfil-to-outbound-sink.json +9 -0
  45. weir_scan-0.1.0/src/weir/rules_commons/loader.py +28 -0
  46. weir_scan-0.1.0/src/weir/schema/__init__.py +0 -0
  47. weir_scan-0.1.0/src/weir/schema/_digest.py +75 -0
  48. weir_scan-0.1.0/src/weir/schema/baseline.py +208 -0
  49. weir_scan-0.1.0/src/weir/schema/delta.py +168 -0
  50. weir_scan-0.1.0/src/weir/schema/dialect.py +103 -0
  51. weir_scan-0.1.0/src/weir/schema/flowfact.py +179 -0
  52. weir_scan-0.1.0/src/weir/schema/trace.py +143 -0
  53. weir_scan-0.1.0/src/weir/taint/__init__.py +12 -0
  54. weir_scan-0.1.0/src/weir/taint/_types.py +24 -0
  55. weir_scan-0.1.0/src/weir/taint/canon.py +9 -0
  56. weir_scan-0.1.0/src/weir/taint/engine.py +99 -0
  57. weir_scan-0.1.0/src/weir/taint/reachability.py +33 -0
  58. weir_scan-0.1.0/tests/test_adapter_contract.py +44 -0
  59. weir_scan-0.1.0/tests/test_adapter_map.py +204 -0
  60. weir_scan-0.1.0/tests/test_adapter_wire.py +177 -0
  61. weir_scan-0.1.0/tests/test_baseline.py +274 -0
  62. weir_scan-0.1.0/tests/test_catalog.py +64 -0
  63. weir_scan-0.1.0/tests/test_catalog_digest.py +119 -0
  64. weir_scan-0.1.0/tests/test_cli.py +221 -0
  65. weir_scan-0.1.0/tests/test_contract_doc_drift.py +15 -0
  66. weir_scan-0.1.0/tests/test_delta.py +315 -0
  67. weir_scan-0.1.0/tests/test_dialect.py +101 -0
  68. weir_scan-0.1.0/tests/test_evaluate.py +221 -0
  69. weir_scan-0.1.0/tests/test_flowfact.py +263 -0
  70. weir_scan-0.1.0/tests/test_gauge.py +173 -0
  71. weir_scan-0.1.0/tests/test_gauge_ladder.py +37 -0
  72. weir_scan-0.1.0/tests/test_graph_builder.py +76 -0
  73. weir_scan-0.1.0/tests/test_labeler.py +87 -0
  74. weir_scan-0.1.0/tests/test_packaged_readme_drift.py +14 -0
  75. weir_scan-0.1.0/tests/test_readme_truth.py +55 -0
  76. weir_scan-0.1.0/tests/test_remediation_sources.py +42 -0
  77. weir_scan-0.1.0/tests/test_report.py +125 -0
  78. weir_scan-0.1.0/tests/test_rule_loader.py +58 -0
  79. weir_scan-0.1.0/tests/test_sample_data_drift.py +19 -0
  80. weir_scan-0.1.0/tests/test_schema_digest.py +96 -0
  81. weir_scan-0.1.0/tests/test_taint_engine.py +145 -0
  82. weir_scan-0.1.0/tests/test_trace_schema.py +132 -0
@@ -0,0 +1,10 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ .mypy_cache/
7
+ .uv_cache/
8
+ dist/
9
+ build/
10
+ *.egg-info/
@@ -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
+ [![CI](https://github.com/IdoGol24/weir/actions/workflows/ci.yml/badge.svg)](https://github.com/IdoGol24/weir/actions/workflows/ci.yml) [![PyPI](https://img.shields.io/pypi/v/weir-scan)](https://pypi.org/project/weir-scan/) [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](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
+ [![CI](https://github.com/IdoGol24/weir/actions/workflows/ci.yml/badge.svg)](https://github.com/IdoGol24/weir/actions/workflows/ci.yml) [![PyPI](https://img.shields.io/pypi/v/weir-scan)](https://pypi.org/project/weir-scan/) [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](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,5 @@
1
+ """Input adapters: foreign telemetry -> CanonicalTrace.
2
+
3
+ Outside the pure analysis core (the purity contract forbids core modules
4
+ from importing this package) and constrained by its own contract to reach
5
+ only the weir.schema seam."""
@@ -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))