Skip to content

Release 0.10.0, without the tag - #188

Merged
rohanrkamath merged 2 commits into
mainfrom
release/0.10.0
Sep 13, 2026
Merged

rohanrkamath merged 2 commits into
mainfrom
release/0.10.0

Conversation

@arpanghoshal

@arpanghoshal arpanghoshal commented Sep 13, 2026

Copy link
Copy Markdown
Member

Item 6 of v0.10. Do not tag — the tag is the maintainer's. Pairs with ctrlrun-docs
release/0.10.0
.

What it does

Version 0.10.0. CHANGELOG [0.10.0], undated, listing what each item added and every behaviour
that became stricter with what it did before
. §11 written in one pass.

§11's finding

Every sentence this document wrote about what a later item would do was wrong.

Three review rounds ran and the citation discipline worked: all 44 line citations round three
checked were exact. What broke was one shape — §3.4.3's "until item 2 lands", §4.3's check 2
described as though it had a source, §5.2's "move observe mode's policy_unapproved", §6.4's scan
line, §4.2's "the loader checks that correspondence". Each was settled prose about code that did not
exist yet; each shipped differently or not at all, and nothing turned red in between.

The rule: a spec sentence whose truth depends on a later item is a test that item owes, named in
that item's table, or it is not in the document.

§5.2 is amended to describe what shipped — the unit of ordering is the position, not the reason,
because approval_required and precondition_changed are both in BLOCKED_APPROVAL_REASONS and
enforce raises them on opposite sides of _in_scope.

The upgrade check, against the released 0.9.0 from PyPI

Not a fixture — pip install ctrlrun==0.9.0 into a clean venv:

0.9.0 wrote: 3 receipts; chain ok: True; schemas: ['ctrlrun.receipt/v6']
this build:  migration runs, store opens
             schemas in one chain: ['ctrlrun.receipt/v6', 'ctrlrun.receipt/v7']
             chain verifies ACROSS it: True (verified 4, breaks 0)
0.9.0 again: reads 4 receipts, chain ok, breaks []
after ONE created_via='hop' row:
             0.9.0 decides an UNRELATED principal's action: False authority_unreadable

The irreversible step is creating the first hop, not installing 0.10.0. Kept as tests so a later
change goes red in the ordinary suite rather than only in a rehearsal.

§7.3's exit criterion, met — and what meeting it found

examples/authority-escalation now pins an upstream. G25, G26 and G27 all grade PASS on it,
each the same under --only as in a full run, and the parametrized agreement test covers all three.

Adding that pin found one more defect, which is why the example earns its place: verify drives
scenarios in-process, where a pinned action refuses on every call, so G2 selected the pinned
action and reported the kernel broken. select() now skips a pinned action unless a scenario asks
for it by name — G27's and nobody else's.

Release verification, from a fresh clone

full gate (Postgres):  4299 passed, 2 skipped   +  66 serial
ctrlrun verify:        21/21 pass, 6 N/A
ctrlrun demo:          ok, no network
build:                 ctrlrun-0.10.0.tar.gz and .whl
wheel diff vs 0.9.0:   55 -> 56 files; added ctrlrun/upstream.py; removed none

import ctrlrun pulls in no extra and does not load verify. Runtime dependencies are still
pyyaml and click.

Two defects this pass found, and one of them is on main now

A high-severity CodeQL alert, currently open on main. ssl.create_default_context() in
observe_upstream "allows TLSv1 and TLSv1_1". It does not on today's interpreter, but the shape is
right: check 3's handshake is what decides whether the gateway starts, and a floor that is only a
default is one a system-wide OpenSSL configuration or a future release can lower. It is stated now.
A caller-supplied SSLContext is untouched; it is the caller's. The fix was pushed to item 4a's
branch minutes after #186 merged, so it rides in here rather than theremain is red on this
until this merges.

The test is worth more than the line, and the first version of it was wrong. It asserted the floor
on the context as built and passed with the line deleted, because the default already sets
TLS 1.2 here: mutation pattern 3 exactly, the environment already preventing what the test forbids.
The double now hands back a context whose floor has been lowered and asserts the function raised
it again. Mutated: line removed, T495d fails; restored, 18 pass.

A generator in the other repository that strips the licence this one requires. Running the docs
generators for this release returned nineteen cookbook files modified, every one with its two SPDX
lines removed. test_every_source_file_carries_its_copyright_and_license requires them on every
.py and .sh under examples/; nineteen of those files are written by ctrlrun-docs'
render_cookbook.py, which never emitted them. They are committed here because they were added by
hand there, so nothing was red, and the next --write would have turned this repository's own
suite red on output the other one produced. Fixed on the docs side, with a test on the side that
writes the files. No change here — the generator now reproduces what is already committed,
19 recipes, 0 drifted.

Also

The SPDX header on the new test, the citation's version, and both adapters' kernel ranges widening
to <0.11 — v0.5's adapter contract is not reopened, so 0.10 is admissible.

Known, and named rather than left

  • Three CLAIMS.md references do not resolve (action.py:79, migrations.py:107 twice). They
    fail identically on the tree before this milestone; not v0.10's, not fixed here.
  • The acceptance-test audit, run rather than remembered. Of T470 to T507, eight numbers
    had no test
    . Four are covered under another name and are a naming debt: T481 inside T474's
    not_mine case, T482 in test_hop_surfaces, T484 by the upgrade suite, T500 by T499a.
    T488 and T499 exist only as T488a/b/c and T499a. Two were genuinely uncovered and are
    written in this PR
    (T483, T486). Two remain: T480, that a hop payload's
    params.metadata.agent_id cannot name its own principal — true by construction today, since the
    parser reads only hop and task from that bag, so what is missing is the regression guard, not
    the property; and T502, the four v0.9 regressions as regression tests. §1's "a number never
    means two things" is not true today.
  • A hop created inside an action is not named on its creator's receipt; §3.4's issuer row
    describes it, and rule 3 holds through DELEGATION_CREATED and its action_id instead.
  • Receipt.hop is read from a context variable at receipt time, so a nested evaluate inside
    an executor overwrites it. Pre-existing in class for Receipt.task since v0.9.

Two more acceptance tests, added here

T483 asserts §3.3's exact key set on a hop refusal's event, as a set and on both rows, so a
field added later goes red rather than shipping. The negative half is the point: a refusal carries
the id the caller already holds and the name of the row that stopped it, never the patterns, the
limits, the subject or the expiry. Mutated: one permitted_actions key added to the payload, T483
fails.

T486 drives a resume from inside an unrelated ambient task= and hop=. A relay serving
several agents resumes a suspended leg inside its own Control.hop(...); a resume that read the
ambient value decides one agent's action against another's envelope, fail-open whenever the ambient
hop is wider. The first version was not enough and a mutation run said so — with both values
recorded as strings, a reader that falls back to ambient only when the event's is absent passes.
The added case is a leg that recorded {"task": null, "hop": null}. Both mutations now fail.

Not merging and not tagging.

@coderabbitai

coderabbitai Bot commented Sep 13, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 30 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: ca7c5dc1-5be6-4ba2-958b-ae28bf30af66

📥 Commits

Reviewing files that changed from the base of the PR and between 73a1f29 and d3f7f8c.

📒 Files selected for processing (16)
  • CHANGELOG.md
  • CITATION.cff
  • adapters/langgraph/README.md
  • adapters/langgraph/pyproject.toml
  • adapters/langgraph/src/ctrlrun_langgraph/__init__.py
  • adapters/openai-agents/README.md
  • adapters/openai-agents/pyproject.toml
  • adapters/openai-agents/src/ctrlrun_openai_agents/__init__.py
  • docs/SPEC-v0.10.md
  • examples/authority-escalation/ctrlrun.yaml
  • pyproject.toml
  • src/ctrlrun/upstream.py
  • src/ctrlrun/verify/scenarios.py
  • tests/test_upgrade_0_9_to_0_10.py
  • tests/test_upstream_pinning.py
  • tests/test_verify_authority.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread src/ctrlrun/upstream.py Fixed
Version 0.10.0. The CHANGELOG [0.10.0] section is undated, lists what each
item added, and lists every behaviour that became stricter with what it did
before.

Section 11 written in one pass. Its finding: every sentence this document
wrote about what a LATER ITEM would do was wrong. Three review rounds ran,
all 44 citations round three checked were exact, and the citation
discipline worked. What broke was one shape: section 3.4.3's "until item 2
lands", section 4.3's check 2 described as though it had a source, section
5.2's "move observe mode's policy_unapproved", section 6.4's scan line,
section 4.2's "the loader checks that correspondence". Each was settled
prose about code that did not exist yet; each shipped differently or not at
all, and nothing turned red in between. The rule that follows is that a
spec sentence whose truth depends on a later item is a test that item owes,
named in that item's table, or it is not in the document.

Section 5.2 amended to describe what shipped. Round three adjudicated: keep
the code, fix the text. The unit of ordering is the POSITION, not the
reason, because approval_required and precondition_changed are both members
of BLOCKED_APPROVAL_REASONS and enforce raises them on opposite sides of
_in_scope.

The upgrade check ran against the RELEASED 0.9.0 from PyPI, not a fixture.
A store written by 0.9.0, opened by this build: the migration runs, the
chain verifies across the v6/v7 boundary (4 receipts, 0 breaks), and 0.9.0
then reads it back cleanly. Then one hop is created and 0.9.0 answers
authority_unreadable for an UNRELATED principal's action, which is section
9.3's claim measured: the irreversible step is creating the first hop, not
installing 0.10.0. Kept as tests so a later change goes red in the ordinary
suite rather than only in a release rehearsal.

Section 7.3's exit criterion is met. examples/authority-escalation now
pins an upstream, and G25, G26 and G27 all grade PASS on it, each the same
under --only as in a full run; the parametrized agreement test covers all
three.

Adding that pin found one more defect, which is why the example earns its
place: verify drives its scenarios in-process, where a pinned action
refuses on every call, so G2 selected the pinned action and reported the
kernel broken. select() now skips a pinned action unless a scenario asks
for it by name, which is G27's and nobody else's.

Also: the SPDX header on the new test, the citation's version, and the two
adapters' kernel ranges, which widen to admit 0.10 because v0.5's adapter
contract is not reopened.

Gate with Postgres: 4441 passed, 0 skipped. Demo runs with no network.
`import ctrlrun` pulls in no extra and does not load verify. Runtime
dependencies are still pyyaml and click.

Signed-off-by: arpan <contact@arpanghoshal.com>
… lowered default

CodeQL, high, on `src/ctrlrun/upstream.py:120`: `create_default_context` "allows TLSv1 and
TLSv1_1". It does not on this interpreter, but the alert is right about the shape. Check 3 is the
handshake that decides whether the gateway starts, and a floor that is only a default is one a
system-wide OpenSSL configuration or a future release can lower. Now stated. A caller-supplied
`SSLContext` is not touched; it is the caller's.

The test is worth more than the line. The first version asserted the floor on the context as built
and **passed with the line deleted**, because the default already sets TLS 1.2 here: mutation
pattern 3, the environment already preventing what the test forbids. The double now hands back a
context whose floor has been lowered, the way a permissive system configuration would, and asserts
the function raised it again. Mutated: line removed, T495d fails; restored, 18 pass.

Not a handshake against a TLS 1.1 listener, for a reason from the same list: such a listener needs
a certificate the default context does not trust, so it is refused for the certificate and the
version is never reached.

Signed-off-by: arpan <contact@arpanghoshal.com>
@rohanrkamath
rohanrkamath merged commit f3f9096 into main Sep 13, 2026
15 of 16 checks passed
@rohanrkamath
rohanrkamath deleted the release/0.10.0 branch September 13, 2026 22:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants