Metadata-Version: 2.4
Name: onetrace
Version: 0.2.2
Summary: onetrace SDK: stage-receipt records and the verbs diff, localize, reproduce
Author-email: Shamik Saha <shamik.saha.rcciit@gmail.com>
License-Expression: Apache-2.0
Project-URL: Issues, https://github.com/shamiksaharcciit-oss/onetrace-issues
Keywords: provenance,reproducibility,data-lineage,stage-receipt,audit-trail
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: onetrace-verify<0.3,>=0.2.1
Provides-Extra: test
Requires-Dist: pytest>=8; extra == "test"
Requires-Dist: pytest-asyncio; extra == "test"
Requires-Dist: pytest-timeout>=2.3; extra == "test"
Requires-Dist: numpy<3,>=2.1; extra == "test"
Requires-Dist: pydantic<3,>=2; extra == "test"
Requires-Dist: build; extra == "test"
Requires-Dist: setuptools>=77; extra == "test"
Requires-Dist: pytest-xdist>=3; extra == "test"
Requires-Dist: jsonschema<5,>=4.18; extra == "test"
Requires-Dist: pypdf==6.19.0; extra == "test"
Provides-Extra: crypto
Requires-Dist: asn1crypto==1.5.1; extra == "crypto"
Requires-Dist: cffi==2.1.1; extra == "crypto"
Requires-Dist: cryptography==50.0.1; extra == "crypto"
Requires-Dist: pycparser==3.0; extra == "crypto"
Requires-Dist: rfc3161-client==1.0.9; extra == "crypto"
Requires-Dist: typing-extensions==4.16.0; python_version < "3.11" and extra == "crypto"
Provides-Extra: langchain
Requires-Dist: beautifulsoup4==4.15.0; extra == "langchain"
Requires-Dist: langchain==1.4.2; extra == "langchain"
Requires-Dist: langchain-classic==1.0.8; extra == "langchain"
Requires-Dist: langchain-community==0.4.2; extra == "langchain"
Requires-Dist: langchain-core==1.6.5; extra == "langchain"
Requires-Dist: langchain-protocol==0.0.19; extra == "langchain"
Requires-Dist: langchain-text-splitters==1.1.2; extra == "langchain"
Requires-Dist: numpy==2.5.3; extra == "langchain"
Requires-Dist: pypdf==6.19.0; extra == "langchain"
Provides-Extra: llamaindex
Requires-Dist: llama-index-core==0.14.25; extra == "llamaindex"
Requires-Dist: llama-index-readers-file==0.7.0; extra == "llamaindex"
Requires-Dist: llama-index-instrumentation==0.6.0; extra == "llamaindex"
Requires-Dist: llama-index-workflows==2.24.1; extra == "llamaindex"
Requires-Dist: pypdf==6.19.0; extra == "llamaindex"
Requires-Dist: numpy==2.4.6; extra == "llamaindex"
Provides-Extra: langflow
Requires-Dist: langflow-base==1.12.3; extra == "langflow"
Requires-Dist: lfx==1.12.3; extra == "langflow"
Dynamic: license-file

# onetrace

onetrace records one hash-chained receipt per stage of a Python pipeline, so anyone can verify the record, with no network call, using a verifier that doesn't trust the code that wrote it, and compare two runs stage by stage. It does not check whether a pipeline's output is *true*: it shows what each recorded stage received and produced, and that the record hasn't been altered since.

- **What a run folder holds.** Each stage's outputs, which can include documents, prompts and answers, and fingerprints (SHA-256 digests) of its inputs: inputs are fingerprinted, not stored. Treat run folders like logs that may contain sensitive data.
- **Overhead.** 23.6–31.4 ms per recorded stage, in one run of the decorated worked examples on one shared machine; [how it was measured, and how to measure it on yours](#how-the-overhead-is-measured), is below.
- **No telemetry.** onetrace sends nothing anywhere, and recording a run makes no network connection. A test runs a decorated run and its verification under a guard that refuses any network access, and fails if anything tries (`tests/test_no_telemetry.py`). The one command that contacts a network is `onetrace anchor`, and only when you run it.

## Install and quickstart

<!-- quickstart: run -->
```
pip install onetrace
```

`onetrace-verify`, the reference verifier, comes with it.

A pipeline in two modules, with two decorators added and nothing else changed. Save `steps.py`:

<!-- quickstart: file steps.py -->
```python
import onetrace as ot

CORPUS = [
    "The warranty covers manufacturing defects for twelve months.",
    "Shipping delays are handled by the courier, not the warranty.",
    "Batteries have a separate six-month warranty.",
]


@ot.stage("retrieve", instrument=ot.pkg("word-overlap", "onetrace", kind="retriever", config={"top_k": 2}))
def retrieve(question: str) -> list[str]:
    words = set(question.lower().rstrip("?").split())
    return sorted(CORPUS, key=lambda p: -len(words & set(p.lower().rstrip(".").split())))[:2]
```

and `app.py`:

<!-- quickstart: file app.py -->
```python
import onetrace as ot
from steps import retrieve


@ot.stage("answer", instrument=ot.pkg("first-passage", "onetrace", kind="model-call"))
def answer(question: str, passages: list[str]) -> str:
    return passages[0]


@ot.run(stages=["retrieve", "answer"])
def main(question: str) -> str:
    return answer(question, retrieve(question))


if __name__ == "__main__":
    print(main("How long does the warranty cover defects?"))
```

`@ot.run` marks the function that does the whole job once; `@ot.stage` marks each step. `retrieve` is defined in another module than the run, so the run lists its stages. `ot.pkg` names the installed distribution whose version is recorded: this demo names `onetrace`; your code names the library that does the work.

<!-- quickstart: run -->
```
python app.py
```

<!-- quickstart: output -->
```
The warranty covers manufacturing defects for twelve months.
```

The run is a folder under `runs/`. `onetrace doctor` checks the installation and verifies the most recent run:

<!-- quickstart: run -->
```
onetrace doctor
```

<!-- quickstart: output -->
```
...
6 checks; all passed
```

Switch recording off with `ONETRACE_DISABLE=1`: the decorators pass straight through and nothing is written. Take onetrace out by deleting the import and the decorators.

<!-- quickstart: run os=posix -->
```
ONETRACE_DISABLE=1 python app.py
```

<!-- quickstart: output -->
```
The warranty covers manufacturing defects for twelve months.
```

[docs/quickstart.md](https://oneproof.dev/onetrace/quickstart.md) starts from one stage, says what that run records and what it doesn't, and shows switching off on Windows too. To instrument an existing pipeline with a coding agent, give it [docs/agents/instrument.md](https://oneproof.dev/onetrace/agents/instrument.md). Everything the decorators record, and their defaults, is in [the manual's Decorators section](https://oneproof.dev/onetrace/manual.md#16-decorators). For full control without decorators, the Recorder API is in [the manual](https://oneproof.dev/onetrace/manual.md#5-integrating-onetrace-into-your-code-step-by-step).

## How the overhead is measured

In one run, recording added 23.6–31.4 ms per stage: the median for each worked example. This is one run on one shared machine. To know the cost on your own machine, run `tools/measure_overhead.py` from this repository; it is not part of the installed package.

- **What it runs:** the decorated version of each worked example under `examples/`: `tiny` (4 stage receipts), `plain_python_control` (9) and `dag` (7). Each is called in one Python process, writing its run to a fresh folder that is removed afterwards.
- **How many times:** the script's defaults, 30 pairs per example, after 3 warm-up pairs that are discarded. Each pair is one run recorded and one with `ONETRACE_DISABLE=1`: the same functions, unrecorded. The two kinds alternate, so any drift in the machine falls on both.
- **The figure:** for each pair, the recorded run's time minus the switched-off run's time, divided by the number of stage receipts the recorded run wrote; the median for each example.
- **The run:** an AMD EPYC 7543 server, Linux, Python 3.12.11, with a quota of 12.75 CPUs, using the script's defaults. The host's load average when the run started: 2.89 over 1 minute, 5.69 over 5 minutes, 8.29 over 15 minutes. Medians per stage: `tiny` 26.3 ms, `plain_python_control` 31.4 ms, `dag` 23.6 ms.

## Five verdicts

Every stage in a comparison gets exactly one of five verdicts, and nothing else:

- **same** — the stage's output digest matches on both sides.
- **FIRST DIFFERENCE** — the first stage, in order, whose output digest does not match.
- **downstream** — after the first difference, still different; the divergence propagated.
- **reconverged** — after diverging, a later stage's output matches again (different work,
  same result — this happens in practice and is reported honestly, not hidden).
- **COULD NOT CHECK** — the stage can't be evaluated (an unimplemented format version, a
  declared boundary, a file that can't be read). This is not a pass, and it is not silently
  folded into "same."

A verdict is always about a stage's **output**. A difference in the instrument, its
configuration, or the declared constants never changes a verdict by itself — it appears as an
annotation alongside the ladder, naming exactly what differs, so a `same` result never hides that
something about *how* the output was produced changed even though the bytes didn't.

`diff` and `localize` each also report one overall result word for the whole comparison
(`identical`, `diverged`, `not comparable`, `refused`, or `could not check`, with exit codes
0/1/2/3/4 respectively — `localize` on a single run instead reports `clean` or `located`, exit
0 or 1); `reproduce` reports each stage as `REPRODUCED`, `DIVERGED`, or `COULD NOT CHECK`.

## Documentation and the trust model

This package's **source distribution** ships its own documentation, rather than linking out to
it: `docs/` (the format in plain words, the five verdicts, the three verbs, one page per adapter,
what onetrace does not claim, and `docs/manual.md`, the full developer manual), the stage-receipt
format itself (an Internet-Draft, `draft/`), and the reference verifier with its rejection
vectors (`verifier/`). A wheel install (`pip install onetrace`) does not include these — get the
sdist instead: `pip download --no-binary :all: --no-deps onetrace`, then unpack the `.tar.gz`.

**The verifier does not trust the SDK that emitted a record.** Every record this SDK produces is
checked the same way a record from any other emitter would be — the verifier reads bytes, not
intentions, and it owes nothing to the code that wrote them.

**What a run folder stores.** Outputs are kept: a run folder holds each stage's outputs, which can include documents, prompts and answers. Inputs are fingerprinted, not stored: a receipt records an input's digest, length and trust class, never its bytes. So treat run folders like logs that may contain sensitive data. `ot.chunks(..., keep_text=True)` adds chunk text to them; without it, a chunk index holds digests only.

## Stability

The current record format is `stage-receipt/0.2` (format major version `0`), specified by an
Internet-Draft that has not yet reached RFC status. Anything in the format — required members,
canonical-form rules, the rejection vectors — may still change before major version `1`. Format
major version is carried in every record: a manifest declaring one the reference verifier does
not implement is refused outright, by name, rather than silently accepted (verify a manifest with
an unrecognized `format` and it prints `[REFUSED] manifest: format -- ...` and exits non-zero); a
verb comparing individual stages reports the stage's own verdict as `COULD NOT CHECK` for the
same reason. Neither ever guesses.

## License, security, and reporting a problem

Licensed under Apache-2.0 (`LICENSE`, in the source distribution).

This is pre-release software (see the classifiers in `pyproject.toml`). See `SECURITY.md`
(in the source distribution) for what's in scope; **report a vulnerability to
onetrace@oneproof.dev**, not as a public issue.
For anything else — a bug, a question, a feature request — open an issue at
[github.com/shamiksaharcciit-oss/onetrace-issues](https://github.com/shamiksaharcciit-oss/onetrace-issues).
