spantruth 0.3.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.
- spantruth-0.3.0/.context/CONTEXT_llm_telemetry_testing_landscape.md +117 -0
- spantruth-0.3.0/.editorconfig +12 -0
- spantruth-0.3.0/.github/CODEOWNERS +1 -0
- spantruth-0.3.0/.github/ISSUE_TEMPLATE/bug.yml +24 -0
- spantruth-0.3.0/.github/ISSUE_TEMPLATE/config.yml +8 -0
- spantruth-0.3.0/.github/ISSUE_TEMPLATE/dispute.yml +30 -0
- spantruth-0.3.0/.github/ISSUE_TEMPLATE/subject.yml +24 -0
- spantruth-0.3.0/.github/PULL_REQUEST_TEMPLATE.md +15 -0
- spantruth-0.3.0/.github/dependabot.yml +20 -0
- spantruth-0.3.0/.github/workflows/ci.yml +106 -0
- spantruth-0.3.0/.github/workflows/cla.yml +26 -0
- spantruth-0.3.0/.github/workflows/release.yml +53 -0
- spantruth-0.3.0/.github/workflows/report.yml +61 -0
- spantruth-0.3.0/.gitignore +12 -0
- spantruth-0.3.0/.plans/PLAN_FEAT_run-anywhere.md +288 -0
- spantruth-0.3.0/.pre-commit-config.yaml +14 -0
- spantruth-0.3.0/.python-version +1 -0
- spantruth-0.3.0/AGENTS.md +23 -0
- spantruth-0.3.0/CHANGELOG.md +139 -0
- spantruth-0.3.0/CITATION.cff +18 -0
- spantruth-0.3.0/CONTRIBUTING.md +96 -0
- spantruth-0.3.0/GOVERNANCE.md +24 -0
- spantruth-0.3.0/LICENSE +202 -0
- spantruth-0.3.0/MAINTAINERS.md +5 -0
- spantruth-0.3.0/PKG-INFO +259 -0
- spantruth-0.3.0/PLAN.md +275 -0
- spantruth-0.3.0/README.md +235 -0
- spantruth-0.3.0/action.yml +115 -0
- spantruth-0.3.0/docs/assets/report.png +0 -0
- spantruth-0.3.0/known_issues.toml +52 -0
- spantruth-0.3.0/pyproject.toml +80 -0
- spantruth-0.3.0/results/2026-11-03-pinned.json +21941 -0
- spantruth-0.3.0/src/spantruth/__init__.py +3 -0
- spantruth-0.3.0/src/spantruth/calls.py +181 -0
- spantruth-0.3.0/src/spantruth/checks.py +210 -0
- spantruth-0.3.0/src/spantruth/cli.py +214 -0
- spantruth-0.3.0/src/spantruth/conventions.py +128 -0
- spantruth-0.3.0/src/spantruth/driver.py +168 -0
- spantruth-0.3.0/src/spantruth/expectations.py +116 -0
- spantruth-0.3.0/src/spantruth/fixtures/openai-responses/NOTICE.md +30 -0
- spantruth-0.3.0/src/spantruth/fixtures/openai-responses/stream.missing-finish-abort.json +80 -0
- spantruth-0.3.0/src/spantruth/mockserver.py +121 -0
- spantruth-0.3.0/src/spantruth/model.py +115 -0
- spantruth-0.3.0/src/spantruth/paths.py +26 -0
- spantruth-0.3.0/src/spantruth/receiver.py +164 -0
- spantruth-0.3.0/src/spantruth/recorded.py +51 -0
- spantruth-0.3.0/src/spantruth/reference.py +276 -0
- spantruth-0.3.0/src/spantruth/report.py +179 -0
- spantruth-0.3.0/src/spantruth/rules.py +91 -0
- spantruth-0.3.0/src/spantruth/runner.py +244 -0
- spantruth-0.3.0/src/spantruth/scenarios.py +353 -0
- spantruth-0.3.0/src/spantruth/site.py +234 -0
- spantruth-0.3.0/src/spantruth/subjects.py +191 -0
- spantruth-0.3.0/src/spantruth/wire_anthropic.py +163 -0
- spantruth-0.3.0/src/spantruth/wire_openai.py +178 -0
- spantruth-0.3.0/subjects/_base.toml +20 -0
- spantruth-0.3.0/subjects/anthropic-sdk/instrument.py +9 -0
- spantruth-0.3.0/subjects/anthropic-sdk/subject.toml +7 -0
- spantruth-0.3.0/subjects/control-faulty/expected.toml +22 -0
- spantruth-0.3.0/subjects/control-faulty/instrument.py +9 -0
- spantruth-0.3.0/subjects/control-faulty/subject.toml +5 -0
- spantruth-0.3.0/subjects/control-honest/instrument.py +7 -0
- spantruth-0.3.0/subjects/control-honest/subject.toml +5 -0
- spantruth-0.3.0/subjects/openinference/instrument.py +9 -0
- spantruth-0.3.0/subjects/openinference/subject.toml +8 -0
- spantruth-0.3.0/subjects/openllmetry/instrument.py +9 -0
- spantruth-0.3.0/subjects/openllmetry/subject.toml +8 -0
- spantruth-0.3.0/subjects/otel-official/instrument.py +9 -0
- spantruth-0.3.0/subjects/otel-official/subject.toml +11 -0
- spantruth-0.3.0/tests/test_action.py +109 -0
- spantruth-0.3.0/tests/test_checks.py +208 -0
- spantruth-0.3.0/tests/test_controls_e2e.py +39 -0
- spantruth-0.3.0/tests/test_expectations.py +214 -0
- spantruth-0.3.0/tests/test_packaging.py +148 -0
- spantruth-0.3.0/tests/test_recorded.py +67 -0
- spantruth-0.3.0/tests/test_report.py +184 -0
- spantruth-0.3.0/tests/test_runner.py +33 -0
- spantruth-0.3.0/tests/test_servers.py +115 -0
- spantruth-0.3.0/tests/test_subjects.py +230 -0
- spantruth-0.3.0/tests/test_wire.py +143 -0
- spantruth-0.3.0/tests/test_workflows.py +31 -0
- spantruth-0.3.0/uv.lock +943 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# The landscape around testing LLM telemetry values
|
|
2
|
+
|
|
3
|
+
Researched on 2026-11-03. Every number has its source and date. Anything marked "unverified" was not
|
|
4
|
+
confirmed at the source. Re-check a number before you quote it in public.
|
|
5
|
+
|
|
6
|
+
## Prior art
|
|
7
|
+
|
|
8
|
+
Nothing found on 2026-11-03 does all of these together: a fake provider, the real provider SDK, the
|
|
9
|
+
production instrumentation packages, an OTLP receiver, and values compared with what the provider sent,
|
|
10
|
+
including failure paths.
|
|
11
|
+
|
|
12
|
+
| Project | What it checks | Difference from a value check |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| [semantic-conventions-genai `reference/`](https://github.com/open-telemetry/semantic-conventions-genai/tree/main/reference) | Hand-written reference instrumentations (built on `opentelemetry-util-genai`) of about 40 client SDKs and frameworks, run against a deterministic mock server. The [conformance runner](https://github.com/open-telemetry/semantic-conventions-conformance) validates the captured telemetry with Weaver and generates a matrix of supported attributes. | The instrumentation is a reference, not the package users install. The checks are names, types and required attributes, not equality with the provider's numbers. |
|
|
15
|
+
| [Weaver live-check](https://github.com/open-telemetry/weaver/blob/main/docs/usage.md) | Received OTLP against a semantic-convention registry, with OPA policies. `--fail-on` sets the exit level. `--diagnostic-format gh_workflow_command` writes workflow annotations. | Conformance to the registry, not to the wire. |
|
|
16
|
+
| [pytest-genai-semconv](https://github.com/Krishnachaitanyakc/pytest-genai-semconv) (0.1.0, 2026-07-06) | A pytest plugin with in-memory span capture and `assert_chat_span(...)` helpers: presence, types, span kind. | Tests your own spans. No fake provider, no real SDK, no failure paths. |
|
|
17
|
+
| [kartsan03/wiretruth](https://github.com/kartsan03/wiretruth) | What the provider SDK returns to the caller, from recorded responses. Owns the `wiretruth` name on PyPI (0.1.0 on 2026-10-11, 0.2.0 on 2026-10-13). | Checks the SDK, not the span. Its fixture format is replayable. |
|
|
18
|
+
| [4ktLuffy/spanproof](https://github.com/4ktLuffy/spanproof) | Sentry SDK integrations, with fixtures validated by the provider SDK's types. | One vendor. |
|
|
19
|
+
| [arthi-arumugam-git/wrong-numbers](https://github.com/arthi-arumugam-git/wrong-numbers) | A field guide to wrong numbers in eval, tracing and cost libraries. | Documentation, not a harness. |
|
|
20
|
+
| [jkelly-dev1/llm-observability-stack](https://github.com/jkelly-dev1/llm-observability-stack) | A personal study. Cost computed from spans was 44% below the provider's usage, because the span copied an `input_tokens` field that excludes the cached prefix. | Not a tool. Evidence for the cache-token class. |
|
|
21
|
+
|
|
22
|
+
The reference scenarios name their metrics `gen_ai.client.inference.usage.input_tokens` (a counter) and
|
|
23
|
+
`gen_ai.client.inference.operation.input_tokens` (a histogram), with matching output, cache and
|
|
24
|
+
reasoning names. Source: `reference/scenarios/anthropic/scenario.py` on the `main` branch, 2026-11-03.
|
|
25
|
+
|
|
26
|
+
## The semantic conventions
|
|
27
|
+
|
|
28
|
+
- The GenAI conventions moved to their own repository,
|
|
29
|
+
[semantic-conventions-genai](https://github.com/open-telemetry/semantic-conventions-genai), in
|
|
30
|
+
semconv v1.42. Every GenAI attribute in its registry has `stability: development`.
|
|
31
|
+
- Token attributes: `gen_ai.usage.input_tokens` (SHOULD include cached tokens),
|
|
32
|
+
`gen_ai.usage.output_tokens` (SHOULD include reasoning tokens), `gen_ai.usage.cache_read.input_tokens`,
|
|
33
|
+
`gen_ai.usage.cache_write.input_tokens` and `gen_ai.usage.reasoning.output_tokens`.
|
|
34
|
+
- The cache-write attribute was `cache_creation.input_tokens` first. The rename is live, and
|
|
35
|
+
instrumentations lag: [pydantic-ai#11779](https://github.com/pydantic/pydantic-ai/issues/11779)
|
|
36
|
+
(2026-10-28). The date of the rename is unverified.
|
|
37
|
+
- Since v1.37.0 (2025-08-25), content goes in `gen_ai.input.messages`, `gen_ai.output.messages` and
|
|
38
|
+
`gen_ai.system_instructions`, or in the `gen_ai.client.inference.operation.details` event.
|
|
39
|
+
|
|
40
|
+
## Who would run a value check
|
|
41
|
+
|
|
42
|
+
pypistats "last month" and npm downloads, read on 2026-11-03. Download counts mix SDK use with
|
|
43
|
+
instrumentation use, so they rank reach, not need.
|
|
44
|
+
|
|
45
|
+
| Library | Downloads a month | Emits OTel GenAI spans |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| LiteLLM | 92.2M | Through its OTel callback |
|
|
48
|
+
| Langfuse SDK | 23.6M | OTel-based SDK |
|
|
49
|
+
| MLflow | 19.9M | OTel export |
|
|
50
|
+
| Pydantic Logfire | 13.0M | Yes |
|
|
51
|
+
| opentelemetry-util-genai (official) | 4.91M | Yes |
|
|
52
|
+
| OpenLLMetry anthropic and openai | 3.02M and 2.86M | Yes |
|
|
53
|
+
| OpenInference openai and anthropic | 2.25M and 0.78M | Its own `llm.*` names |
|
|
54
|
+
| OpenLIT | 278k | Yes |
|
|
55
|
+
| Vercel AI SDK (`ai`, npm) | 118.4M | `experimental_telemetry`, mixed names (unverified) |
|
|
56
|
+
|
|
57
|
+
Provider SDKs: openai 295.9M, anthropic 150.2M, google-genai 137.6M, groq 16.8M and mistralai 16.2M a
|
|
58
|
+
month. On npm: `@anthropic-ai/sdk` 181.4M, `openai` 162.0M and `@google/genai` 91.6M.
|
|
59
|
+
|
|
60
|
+
## Demand: the bug classes repeat
|
|
61
|
+
|
|
62
|
+
Issues and pull requests created between 2026-05-03 and 2026-11-03, with their state on 2026-11-03.
|
|
63
|
+
Four classes repeat across eight independent projects: cache tokens dropped or counted twice, reasoning
|
|
64
|
+
tokens dropped, spans that never end, and one call reported on two spans.
|
|
65
|
+
|
|
66
|
+
- Cache tokens: [openllmetry#4665](https://github.com/traceloop/openllmetry/issues/4665),
|
|
67
|
+
[openllmetry#4583](https://github.com/traceloop/openllmetry/issues/4583) (`prompt_tokens_details` is
|
|
68
|
+
None), [mlflow#26907](https://github.com/mlflow/mlflow/issues/26907) (final counts in `message_delta`
|
|
69
|
+
ignored), [mlflow#26817](https://github.com/mlflow/mlflow/issues/26817),
|
|
70
|
+
[litellm#49558](https://github.com/BerriAI/litellm/pull/49558),
|
|
71
|
+
[langfuse#19483](https://github.com/langfuse/langfuse/pull/19483) and
|
|
72
|
+
[pydantic-ai#11779](https://github.com/pydantic/pydantic-ai/issues/11779).
|
|
73
|
+
- Reasoning tokens: [openllmetry#4660](https://github.com/traceloop/openllmetry/issues/4660) (streamed
|
|
74
|
+
chat), [litellm#49557](https://github.com/BerriAI/litellm/pull/49557) and
|
|
75
|
+
[python-contrib#4987](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4987).
|
|
76
|
+
- Spans that never end: [openllmetry#4608](https://github.com/traceloop/openllmetry/issues/4608),
|
|
77
|
+
[openllmetry#4597](https://github.com/traceloop/openllmetry/issues/4597),
|
|
78
|
+
[openllmetry#4650](https://github.com/traceloop/openllmetry/pull/4650) and
|
|
79
|
+
[openinference#4215](https://github.com/Arize-ai/openinference/pull/4215).
|
|
80
|
+
- One call on two spans: [openllmetry#4609](https://github.com/traceloop/openllmetry/issues/4609).
|
|
81
|
+
- Calls through other entry points record nothing:
|
|
82
|
+
[logfire#2549](https://github.com/pydantic/logfire/issues/2549) (`with_raw_response` and
|
|
83
|
+
`with_streaming_response`).
|
|
84
|
+
- Responses API paths: [openinference#4185](https://github.com/Arize-ai/openinference/issues/4185) and
|
|
85
|
+
[logfire#2565](https://github.com/pydantic/logfire/pull/2565).
|
|
86
|
+
|
|
87
|
+
## How conformance suites became the default
|
|
88
|
+
|
|
89
|
+
From primary sources, read on 2026-11-03.
|
|
90
|
+
|
|
91
|
+
- **[Bowtie](https://github.com/bowtie-json-schema/bowtie)** is the closest model. Each JSON Schema
|
|
92
|
+
implementation runs in a container with a small harness that speaks a JSON-lines protocol on stdin and
|
|
93
|
+
stdout. Harnesses live in the implementers' directories. Bowtie has a report site, shields.io endpoint
|
|
94
|
+
badges, a composite setup action that installs it with uv, and `bowtie summary --format markdown` for
|
|
95
|
+
the job summary. A skipped test carries an issue URL. Its author wrote most early harnesses himself.
|
|
96
|
+
It launched with 12 implementations and now has 24.
|
|
97
|
+
- **test262** and **web-platform-tests** keep known failures in the implementer's repository and
|
|
98
|
+
publish results per implementation (test262.fyi, wpt.fyi).
|
|
99
|
+
- **Kubernetes conformance** gives a trademark to certified products, and the trademark drives
|
|
100
|
+
adoption.
|
|
101
|
+
- **Protocol Buffers conformance** uses a small "testee" per language over stdin and stdout, the same
|
|
102
|
+
idea as the Bowtie harness.
|
|
103
|
+
|
|
104
|
+
Patterns that drove adoption: a tiny harness as the unit of contribution, the harness in the
|
|
105
|
+
implementer's repository, a public result and a badge per implementation, known failures recorded with
|
|
106
|
+
an issue link, a one-step CI path, a neutral home, and a funded maintainer who writes the first
|
|
107
|
+
harnesses.
|
|
108
|
+
|
|
109
|
+
Weaknesses seen: a frozen toolchain (Autobahn is pinned to Python 2), no stable test identity (Bowtie
|
|
110
|
+
matches skips by description), governance that never became formal, and no documented dispute path.
|
|
111
|
+
|
|
112
|
+
Launch posts alone rarely drove adoption: the promptfoo Show HN scored 14 points. Integrations in the
|
|
113
|
+
places users already work mattered more.
|
|
114
|
+
|
|
115
|
+
## Related Context
|
|
116
|
+
|
|
117
|
+
None yet.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* @chrikrah
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: Bug in spantruth
|
|
2
|
+
description: The harness itself misbehaves, for example a cell is inconclusive or a run fails.
|
|
3
|
+
labels: ["bug"]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: what
|
|
7
|
+
attributes:
|
|
8
|
+
label: What happened
|
|
9
|
+
description: The command you ran, and its output.
|
|
10
|
+
render: shell
|
|
11
|
+
validations:
|
|
12
|
+
required: true
|
|
13
|
+
- type: input
|
|
14
|
+
id: version
|
|
15
|
+
attributes:
|
|
16
|
+
label: spantruth version or commit
|
|
17
|
+
validations:
|
|
18
|
+
required: true
|
|
19
|
+
- type: input
|
|
20
|
+
id: env
|
|
21
|
+
attributes:
|
|
22
|
+
label: Operating system and Python version
|
|
23
|
+
validations:
|
|
24
|
+
required: true
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
blank_issues_enabled: false
|
|
2
|
+
contact_links:
|
|
3
|
+
- name: Question or idea
|
|
4
|
+
url: https://github.com/Plexito-de/spantruth/discussions
|
|
5
|
+
about: Ask a question or float an idea in Discussions.
|
|
6
|
+
- name: Security problem
|
|
7
|
+
url: https://github.com/Plexito-de/spantruth/security/advisories/new
|
|
8
|
+
about: Report a vulnerability privately. Do not open a public issue.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: Dispute a finding
|
|
2
|
+
description: A cell on the report is red or yellow and you think the check is wrong.
|
|
3
|
+
labels: ["dispute"]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: |
|
|
8
|
+
Thank you. Maintainers of the library under test are especially welcome here. We decide
|
|
9
|
+
every dispute on one question: does the cited rule say what the check claims?
|
|
10
|
+
- type: input
|
|
11
|
+
id: cell
|
|
12
|
+
attributes:
|
|
13
|
+
label: Subject and scenario
|
|
14
|
+
placeholder: openllmetry, anthropic/stream
|
|
15
|
+
validations:
|
|
16
|
+
required: true
|
|
17
|
+
- type: input
|
|
18
|
+
id: check
|
|
19
|
+
attributes:
|
|
20
|
+
label: Check
|
|
21
|
+
placeholder: output_tokens
|
|
22
|
+
validations:
|
|
23
|
+
required: true
|
|
24
|
+
- type: textarea
|
|
25
|
+
id: why
|
|
26
|
+
attributes:
|
|
27
|
+
label: Why the finding is wrong
|
|
28
|
+
description: Quote the spec, the provider documentation, or your library's documented behavior.
|
|
29
|
+
validations:
|
|
30
|
+
required: true
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: Add a subject, provider or scenario
|
|
2
|
+
description: Propose a library, a provider API, or a failure mode to cover.
|
|
3
|
+
labels: ["enhancement"]
|
|
4
|
+
body:
|
|
5
|
+
- type: dropdown
|
|
6
|
+
id: kind
|
|
7
|
+
attributes:
|
|
8
|
+
label: What to add
|
|
9
|
+
options: [A subject (instrumentation library), A provider, A scenario]
|
|
10
|
+
validations:
|
|
11
|
+
required: true
|
|
12
|
+
- type: textarea
|
|
13
|
+
id: what
|
|
14
|
+
attributes:
|
|
15
|
+
label: Details
|
|
16
|
+
description: Name, link, and for a scenario the provider behavior it reproduces, with a link to the provider documentation.
|
|
17
|
+
validations:
|
|
18
|
+
required: true
|
|
19
|
+
- type: checkboxes
|
|
20
|
+
id: help
|
|
21
|
+
attributes:
|
|
22
|
+
label: Contribution
|
|
23
|
+
options:
|
|
24
|
+
- label: I would like to open the pull request myself.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## What this changes
|
|
2
|
+
|
|
3
|
+
<!-- One or two sentences. Link the issue it closes. -->
|
|
4
|
+
|
|
5
|
+
## How it was tested
|
|
6
|
+
|
|
7
|
+
<!-- The commands you ran and their output. -->
|
|
8
|
+
|
|
9
|
+
## Checklist
|
|
10
|
+
|
|
11
|
+
- [ ] The title is a Conventional Commit, for example `feat(subjects): add logfire`.
|
|
12
|
+
- [ ] A test covers the change, and it fails when the change is reverted.
|
|
13
|
+
- [ ] `control-honest` passes, and `control-faulty` fails exactly its expected checks.
|
|
14
|
+
- [ ] Every new red cell is explained: the line of code that causes it, or a note that I could not find it.
|
|
15
|
+
- [ ] `CHANGELOG.md` is updated under Unreleased.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: github-actions
|
|
4
|
+
directory: /
|
|
5
|
+
schedule:
|
|
6
|
+
interval: weekly
|
|
7
|
+
cooldown:
|
|
8
|
+
default-days: 14
|
|
9
|
+
groups:
|
|
10
|
+
actions:
|
|
11
|
+
patterns: ["*"]
|
|
12
|
+
- package-ecosystem: uv
|
|
13
|
+
directory: /
|
|
14
|
+
schedule:
|
|
15
|
+
interval: weekly
|
|
16
|
+
cooldown:
|
|
17
|
+
default-days: 14
|
|
18
|
+
groups:
|
|
19
|
+
python:
|
|
20
|
+
patterns: ["*"]
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions: {}
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: ci-${{ github.ref }}
|
|
12
|
+
cancel-in-progress: true
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
lint:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
permissions:
|
|
18
|
+
contents: read
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
21
|
+
with:
|
|
22
|
+
persist-credentials: false
|
|
23
|
+
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
24
|
+
- run: uv sync --locked
|
|
25
|
+
- run: uv run ruff check .
|
|
26
|
+
- run: uv run ruff format --check .
|
|
27
|
+
|
|
28
|
+
test:
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
permissions:
|
|
31
|
+
contents: read
|
|
32
|
+
strategy:
|
|
33
|
+
matrix:
|
|
34
|
+
python: ["3.11", "3.12"]
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
37
|
+
with:
|
|
38
|
+
persist-credentials: false
|
|
39
|
+
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
40
|
+
- run: uv sync --locked --python ${{ matrix.python }}
|
|
41
|
+
- run: uv run pytest -m "not slow"
|
|
42
|
+
|
|
43
|
+
controls:
|
|
44
|
+
# The falsification of the whole suite: both controls, end to end, in real subprocesses.
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
permissions:
|
|
47
|
+
contents: read
|
|
48
|
+
steps:
|
|
49
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
50
|
+
with:
|
|
51
|
+
persist-credentials: false
|
|
52
|
+
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
53
|
+
- run: uv sync --locked
|
|
54
|
+
- run: uv run pytest -m slow
|
|
55
|
+
|
|
56
|
+
zizmor:
|
|
57
|
+
runs-on: ubuntu-latest
|
|
58
|
+
permissions:
|
|
59
|
+
contents: read
|
|
60
|
+
security-events: write
|
|
61
|
+
steps:
|
|
62
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
63
|
+
with:
|
|
64
|
+
persist-credentials: false
|
|
65
|
+
- uses: zizmorcore/zizmor-action@85b2b9fb8e3325e7a23d7d0a9d7ba5ae0f999e3e # v0.7.1
|
|
66
|
+
|
|
67
|
+
action:
|
|
68
|
+
# The action as a user runs it. The last run compares with an empty expectation file, so it
|
|
69
|
+
# must fail with exit code 1: a gate that cannot fail has measured nothing. `uses: $/` is
|
|
70
|
+
# GitHub's self-repository syntax (July 2026): this repository at the workflow's own commit,
|
|
71
|
+
# independent of what a step left in the workspace. It is not a typo for `./`.
|
|
72
|
+
runs-on: ubuntu-latest
|
|
73
|
+
permissions:
|
|
74
|
+
contents: read
|
|
75
|
+
steps:
|
|
76
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
77
|
+
with:
|
|
78
|
+
persist-credentials: false
|
|
79
|
+
- uses: $/
|
|
80
|
+
with:
|
|
81
|
+
subject: control-honest
|
|
82
|
+
scenario: |
|
|
83
|
+
openai/basic
|
|
84
|
+
anthropic/stream-early-exit
|
|
85
|
+
- uses: $/
|
|
86
|
+
with:
|
|
87
|
+
subject: control-faulty
|
|
88
|
+
expect: subjects/control-faulty/expected.toml
|
|
89
|
+
scenario: |
|
|
90
|
+
openai/stream-no-usage
|
|
91
|
+
anthropic/cache-hit
|
|
92
|
+
anthropic/stream-early-exit
|
|
93
|
+
- run: touch "$RUNNER_TEMP/empty.toml"
|
|
94
|
+
- id: differs
|
|
95
|
+
uses: $/
|
|
96
|
+
continue-on-error: true
|
|
97
|
+
with:
|
|
98
|
+
subject: control-faulty
|
|
99
|
+
expect: ${{ runner.temp }}/empty.toml
|
|
100
|
+
scenario: openai/stream-no-usage
|
|
101
|
+
artifact-name: spantruth-control-faulty-differs
|
|
102
|
+
- name: The run against an empty expectation file failed with exit code 1
|
|
103
|
+
env:
|
|
104
|
+
OUTCOME: ${{ steps.differs.outcome }}
|
|
105
|
+
CODE: ${{ steps.differs.outputs.exit-code }}
|
|
106
|
+
run: test "$OUTCOME" = failure && test "$CODE" = 1
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: CLA
|
|
2
|
+
|
|
3
|
+
# pull_request_target is required so the check can comment on pull requests from forks.
|
|
4
|
+
# The reusable workflow never checks out or runs code from the pull request.
|
|
5
|
+
on:
|
|
6
|
+
pull_request_target: # zizmor: ignore[dangerous-triggers]
|
|
7
|
+
types: [opened, synchronize, reopened]
|
|
8
|
+
issue_comment:
|
|
9
|
+
types: [created]
|
|
10
|
+
|
|
11
|
+
permissions: {}
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
cla:
|
|
15
|
+
if: >-
|
|
16
|
+
github.event_name == 'pull_request_target' ||
|
|
17
|
+
(github.event.issue.pull_request &&
|
|
18
|
+
(contains(github.event.comment.body, 'I have read the CLA Document') ||
|
|
19
|
+
contains(github.event.comment.body, 'recheck')))
|
|
20
|
+
permissions:
|
|
21
|
+
contents: write
|
|
22
|
+
pull-requests: write
|
|
23
|
+
statuses: write
|
|
24
|
+
uses: Plexito-de/.github/.github/workflows/cla.yml@main # zizmor: ignore[unpinned-uses] our own org's workflow
|
|
25
|
+
with:
|
|
26
|
+
allowlist: "chrikrah,dependabot[bot],github-actions[bot]"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# A release is a tag with notes and a wheel on PyPI. The wheel holds the harness, the CLI and the
|
|
4
|
+
# subjects, so `uvx spantruth` runs without a clone.
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
tags: ["v*"]
|
|
8
|
+
|
|
9
|
+
permissions: {}
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
check:
|
|
13
|
+
# The tag, the package version and the wheel name must agree, or the action ref, the wheel and
|
|
14
|
+
# the results document would each claim a different version.
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
20
|
+
with:
|
|
21
|
+
persist-credentials: false
|
|
22
|
+
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
23
|
+
- run: uv build
|
|
24
|
+
- name: The tag matches the package version
|
|
25
|
+
env:
|
|
26
|
+
TAG: ${{ github.ref_name }}
|
|
27
|
+
run: test -f "dist/spantruth-${TAG#v}-py3-none-any.whl"
|
|
28
|
+
|
|
29
|
+
github-release:
|
|
30
|
+
needs: check
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
permissions:
|
|
33
|
+
contents: write
|
|
34
|
+
steps:
|
|
35
|
+
- uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
|
|
36
|
+
with:
|
|
37
|
+
generate_release_notes: true
|
|
38
|
+
|
|
39
|
+
pypi:
|
|
40
|
+
needs: check
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
# Trusted publishing: PyPI trusts this workflow and this environment, so no token is stored.
|
|
43
|
+
environment: pypi
|
|
44
|
+
permissions:
|
|
45
|
+
contents: read
|
|
46
|
+
id-token: write
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
49
|
+
with:
|
|
50
|
+
persist-credentials: false
|
|
51
|
+
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
52
|
+
- run: uv build
|
|
53
|
+
- uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
name: Report
|
|
2
|
+
|
|
3
|
+
# Runs every subject at its pinned versions and at its latest release, and publishes the
|
|
4
|
+
# matrix to GitHub Pages.
|
|
5
|
+
on:
|
|
6
|
+
schedule:
|
|
7
|
+
- cron: "17 3 * * *"
|
|
8
|
+
push:
|
|
9
|
+
branches: [main]
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
|
|
12
|
+
permissions: {}
|
|
13
|
+
|
|
14
|
+
concurrency:
|
|
15
|
+
group: report
|
|
16
|
+
cancel-in-progress: false
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
run:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
permissions:
|
|
22
|
+
contents: read
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
25
|
+
with:
|
|
26
|
+
persist-credentials: false
|
|
27
|
+
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
28
|
+
- run: uv sync --locked
|
|
29
|
+
# Exit code 3 means the harness could not run a cell or build an environment, for example
|
|
30
|
+
# when a new upstream release fails to install. Publish the report anyway, so the gap shows
|
|
31
|
+
# on the page, and flag the run.
|
|
32
|
+
- name: Pinned versions
|
|
33
|
+
run: |
|
|
34
|
+
rc=0
|
|
35
|
+
uv run spantruth run --out site/pinned.json || rc=$?
|
|
36
|
+
if [ "$rc" -eq 3 ]; then echo "::warning::incomplete pinned run"; elif [ "$rc" -ne 0 ]; then exit "$rc"; fi
|
|
37
|
+
- name: Latest releases
|
|
38
|
+
run: |
|
|
39
|
+
rc=0
|
|
40
|
+
uv run spantruth run --latest --out site/latest.json || rc=$?
|
|
41
|
+
if [ "$rc" -eq 3 ]; then echo "::warning::incomplete latest run"; elif [ "$rc" -ne 0 ]; then exit "$rc"; fi
|
|
42
|
+
- name: Render
|
|
43
|
+
run: |
|
|
44
|
+
uv run spantruth report site/pinned.json --html site/index.html
|
|
45
|
+
uv run spantruth report site/latest.json --html site/latest.html --badges site/badges
|
|
46
|
+
- uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
|
|
47
|
+
with:
|
|
48
|
+
path: site
|
|
49
|
+
|
|
50
|
+
deploy:
|
|
51
|
+
needs: run
|
|
52
|
+
runs-on: ubuntu-latest
|
|
53
|
+
permissions:
|
|
54
|
+
pages: write
|
|
55
|
+
id-token: write
|
|
56
|
+
environment:
|
|
57
|
+
name: github-pages
|
|
58
|
+
url: ${{ steps.deploy.outputs.page_url }}
|
|
59
|
+
steps:
|
|
60
|
+
- id: deploy
|
|
61
|
+
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
|