Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ any change to one appears here.

## [Unreleased]

## [0.11.0] — Evidence

One question: **can the record be trusted after the fact, and kept?**

Every milestone so far added something the receipt records. None asked whether the receipt is
still worth reading a year later, on a database an administrator can write to, after somebody
pruned it. This is the first milestone whose subject is the evidence itself rather than the
decision, and the first that opens by admitting a defect in the thing it is about: the chain has
never detected truncation or append, both reachable in two SQL statements, and both written down
since `SPEC-v0.6.md` §6.4.

### Added

- **`docs/SPEC-v0.11.md`, the v0.11 "Evidence" contract.** Documentation only; the version bump is
Expand Down Expand Up @@ -86,6 +97,29 @@ any change to one appears here.
`anchor_broken` and an anchoring deployment would have to choose between pruning and a
permanent tamper signal.

**"Anchored" means the provider says so, at the pair the checkpoint claims.** The first
implementation took the union of what the provider returned and what the store's own `anchors`
table held, so one `INSERT` beside a forged checkpoint row bought supersession and the row's
hash was never compared to anything. Supersession now comes from `provider.since()` alone, and
the anchor's `(seq, hash)` must be the pair the checkpoint asserts. A local row the provider
does not confirm buys nothing. Found by the independent review the build order required for
this item, which also gave `SPEC-v0.11.md` §4.6 the sentence that says which reading is meant.

**A prune leaves two receipts, and they are distinguishable.** The first records the request,
`--through`, `--older-than` and `--reason`, staged `proposed`; the second records what became
of it, `completed` or `refused`. They were byte-identical at first, and `--older-than` was in
neither, which made the record of a refusal worth nothing.

**The bound comes from the receipts, not from `receipt_chain`.** That row is the one
`SPEC-v0.11.md` §2.1 assumes an attacker rewrites, and deciding `--through` from it meant one
`UPDATE` turned a prefix prune into a full-chain delete that both readers called clean.

**The prune's lock is held across the validation and the delete on both backends.** SQLite's
`pruning()` opened `BEGIN IMMEDIATE` and then every `put_anchor` went through `with connection:`
and committed it, so the prune held the lock for one statement; a failed prune could leave
`missing` and `link_broken` on a chain that was intact when it started. The defect had been
found on Postgres during the item and fixed only there, and SQLite is the default backend.

**A checkpoint is a row, not a receipt field.** A receipt naming itself a checkpoint is a
string in a document, and `SPEC-v0.3.md` §4.3.1 settled that shape. A prune writes a receipt
for a human; the row is what the walk reads.
Expand Down
2 changes: 1 addition & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ authors:
- family-names: Ghoshal
given-names: Arpan
email: contact@arpanghoshal.com
version: 0.10.0
version: 0.11.0
repository-code: https://github.com/CTRLRun/ctrlrun
url: https://github.com/CTRLRun/ctrlrun
license: Apache-2.0
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,9 @@ second. [Run it in your browser](https://docs.ctrlrun.dev/try-it) with nothing i
**Where it stops.** It does not detect prompt injection: it contains the consequence rather
than reading the cause. It cannot promise exactly-once against a remote it does not control, it
refuses to *knowingly* act twice, and it rolls nothing back. Receipts are chained, so an alteration
is detected. They are not signed: alteration is not authorship. The badge above means the
is detected; a truncation at the end and a forged append are not, because the head that would catch
them is a row in the same database, and closing that is what `ctrlrun anchor` is for. They are not
signed: alteration is not authorship. The badge above means the
**declared guarantees pass** in the setup they ran against, and it does not mean secure, safe,
compliant, certified or audited:
[what the badge means](https://docs.ctrlrun.dev/verify#what-the-badge-means)
Expand Down
29 changes: 21 additions & 8 deletions adapters/PUBLISHED.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,23 @@
# `pip install ctrlrun-openai-agents` beside a current kernel then either refuses to resolve or
# silently downgrades ctrlrun to 0.5.x.
#
# This file is the record of the published side, updated by hand **in the same commit that tags
# a release**. `test_a_widened_kernel_range_is_not_shipped_without_a_new_version` compares it to
# the tree: if an adapter's range has moved away from what was published, its version must have
# moved too, or the release is one nobody can install.
# This file is the record of the published side: **what `pip download` gets today**, never what a
# tag about to be cut will publish. `test_a_widened_kernel_range_is_not_shipped_without_a_new_version`
# compares it to the tree: if an adapter's range has moved away from what was published, its
# version must have moved too, or the release is one nobody can install. Update a row **after**
# the upload succeeds, not beside the change that motivates it.
#
# **The 0.11.0 release pass got that wrong and the guard said nothing.** It widened the tree to
# `<0.12` and edited this file to `1.1.0 = <0.12` in the same commit, so the two sides agreed and
# the comparison returned early. PyPI had, and still has, `ctrlrun-langgraph` 1.1.0 declaring
# `ctrlrun<0.11`:
#
# $ curl -s https://pypi.org/pypi/ctrlrun-langgraph/json | jq -r .info.requires_dist[]
# ctrlrun<0.11,>=0.5
#
# Which is the v0.10 defect this file was written for, recurring one release later, in the file.
# A record edited to match a claim is not a record. `RECORDED` in `test_packaging.py` freezes the
# range each published version declares, so raising one without a new version now fails.
#
# The tag carries NO `v`. `publish.yml` reads an adapter tag as everything after the last hyphen,
# so `adapters-langgraph-1.1.0` names 1.1.0 and `adapters-langgraph-v1.1.0` names `v1.1.0` and is
Expand All @@ -27,9 +40,9 @@
# that gets skipped in the run that mattered.

[langgraph]
version = "1.0.0"
kernel = ">=0.5,<0.6"
version = "1.1.0"
kernel = ">=0.5,<0.11"

[openai-agents]
version = "1.0.0"
kernel = ">=0.5,<0.6"
version = "1.1.0"
kernel = ">=0.5,<0.11"
2 changes: 1 addition & 1 deletion adapters/langgraph/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Route a CTRLRun `APPROVE` through **LangGraph's own `interrupt()`**, so the human answers where
your LangGraph users already answer.

- **Supported kernel range:** `ctrlrun>=0.5,<0.11`
- **Supported kernel range:** `ctrlrun>=0.5,<0.12`
- **Supported framework range:** `langgraph>=1.0,<2.0`
- **Primitive reused:** [`interrupt()` and `Command(resume=...)`](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/add-human-in-the-loop/), with a checkpointer. Read 2026-09-05.
- **Framework shape:** resumed in place (SPEC-v0.5 §3.5).
Expand Down
4 changes: 2 additions & 2 deletions adapters/langgraph/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "ctrlrun-langgraph"
version = "1.1.0"
version = "1.2.0"
description = "Route a CTRLRun APPROVE through LangGraph's own interrupt()."
readme = "README.md"
requires-python = ">=3.11"
Expand All @@ -26,7 +26,7 @@ classifiers = [
# surface that has not been written. The README states the same two, and T137 asserts that what
# it states is what CI installed.
dependencies = [
"ctrlrun>=0.5,<0.11",
"ctrlrun>=0.5,<0.12",
"langgraph>=1.0,<2.0",
]

Expand Down
2 changes: 1 addition & 1 deletion adapters/langgraph/src/ctrlrun_langgraph/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
buys one thing over it: the interrupt. If your graph has nowhere for a human to answer, or you
are happy for `ApprovalRequired` to reach your own code, use `@protect` and stop here.

Supported kernel range: `ctrlrun>=0.5,<0.11`. Supported framework range: `langgraph>=1.0,<2.0`.
Supported kernel range: `ctrlrun>=0.5,<0.12`. Supported framework range: `langgraph>=1.0,<2.0`.
`README.md` states both, and what this adapter's binding check is and is not.
"""

Expand Down
2 changes: 1 addition & 1 deletion adapters/openai-agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Route a CTRLRun `APPROVE` through the **OpenAI Agents SDK's own tool-approval interruption**, so
the human answers where this SDK's users already answer.

- **Supported kernel range:** `ctrlrun>=0.5,<0.11`
- **Supported kernel range:** `ctrlrun>=0.5,<0.12`
- **Supported framework range:** `openai-agents>=0.20,<1.0`
- **Primitive reused:** [`needs_approval`, `RunResult.interruptions`, `RunState.approve` / `reject`](https://openai.github.io/openai-agents-python/tools/). Read 2026-09-05.
- **Framework shape:** decided before invocation (SPEC-v0.5 §3.5).
Expand Down
4 changes: 2 additions & 2 deletions adapters/openai-agents/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "ctrlrun-openai-agents"
version = "1.1.0"
version = "1.2.0"
description = "Route a CTRLRun APPROVE through the OpenAI Agents SDK's tool-approval interruption."
readme = "README.md"
requires-python = ">=3.11"
Expand All @@ -25,7 +25,7 @@ classifiers = [
# not before, so `>=0.5` would claim compatibility with a surface not yet written. T137 asserts
# the README states these and that CI ran inside them.
dependencies = [
"ctrlrun>=0.5,<0.11",
"ctrlrun>=0.5,<0.12",
"openai-agents>=0.20,<1.0",
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
**You probably do not need this.** `@protect` covers anything in this process with no adapter
and no framework support. This buys the interrupt and nothing else.

Supported kernel range: `ctrlrun>=0.5,<0.11`.
Supported kernel range: `ctrlrun>=0.5,<0.12`.
Supported framework range: `openai-agents>=0.20,<1.0`.
`README.md` states both, and states why this adapter's binding is **attribution** where
LangGraph's is prevention.
Expand Down
Loading