Skip to content

ctrlrun 0.12.0

Latest

Choose a tag to compare

@github-actions github-actions released this 15 Sep 01:27
· 3 commits to main since this release
ed6b2ec

One question: is the thing this project says about itself checkable?

No new capability. Three claims that were prose became tests, and each of the three was false or
incomplete when the test was written, which is the milestone's whole argument.

Changed

  • The module map is acyclic, and ARCHITECTURE.md §6's rule is now a test. §6 has said
    dependencies point downward only since v0.1, and from v0.7 it was false: a review found
    state -> receipt -> policy -> authority -> state and the sentence was amended to record the
    cycle rather than fix it. Nothing broke at run time, because the two edges out of policy.py
    are function-level, so import ctrlrun resolved in one order and the suite passed for five
    milestones.

    Broken in two places. Decision and POLICY_UNAPPROVED moved to ctrlrun.decision, which
    imports nothing from the package: that was the whole of what a receipt needed from the decider,
    and an evidence type reaching up into it is the edge that most contradicts §6's table. Then
    the policy document grammar -- schemas, the strict YAML loader, the condition parser and
    evaluator, type-strict equality -- moved to ctrlrun.grammar, so authority.py no longer
    imports policy.py at all. SPEC-v0.3.md §4.5 requires the two axes to share one condition
    evaluator, and that is better served than before: the one evaluator is owned by neither axis.

    No public name moved. policy.py re-exports all thirty-four, so
    from ctrlrun.policy import Decision, Condition, parse_conditions resolves to the same objects
    and SPEC-v0.1.md §8's frozen __init__ block is unchanged.

    tests/test_module_graph.py walks every module's AST and separates two questions §6 kept
    conflating: the import-order graph, which is module-level imports, and the layering
    graph, which counts deferred imports and is what the table describes. The recorded cycle is
    invisible to the first, so a guard built only on module-level imports passes on 0.11.0's
    tree
    . Writing it found a second cycle nobody had recorded, jwt_identity and revocation
    sharing a security-critical redirect handler through a deferred import; _NoRedirects moved
    down to revocation.py and keeps logging under ctrlrun.policy's logger name so no operator's
    handler is re-routed.

  • The gateway told MCP clients to call a Python API. A -41002 relayed
    str(ApprovalRequired) verbatim, and that exception carries the decorator's wording: "run
    ctrlrun approve …, then retry inside ctrlrun.with_approval(…)"
    . On the one path where the
    caller may be in any language, and is often a model reading the error as text, it pointed at a
    context manager the caller cannot reach. It now says what the gateway's own documentation
    already said: a human approves and this same call runs.

Added

  • Property tests over generated inputs (tests/test_properties.py, hypothesis). The
    invariant that matters is the one v0.11 item 1 violated: every break a tamper reports names a
    row the store actually holds.
    That defect reported content_altered 99 and missing 100 on
    an eight-row chain, because position came from the document rather than the seq column.
    Reinstating it fails these tests immediately.

    The first version of that property was one tamper is one break, and hypothesis falsified it on
    its second example: altering row n also breaks the link at n + 1. Two is correct and the
    expectation was wrong, which is the same mistake the file exists to catch. derandomize=True,
    so a counterexample found in CI reproduces locally by construction.

  • A CycloneDX SBOM of the wheel, measured from the wheel (scripts/sbom.sh). Generated by
    installing the built wheel into an empty environment and recording what resolves, not by reading
    pyproject.toml: a manifest-derived SBOM is the project's opinion of its own dependencies. The
    answer is two, PyYAML and click. CI generates and checks it on every pull request, and
    release.yml writes it into dist/ before the attestation step, so it is signed with the
    distributions and attached to the release.

    The seed packages are removed before the scan, and that is not cosmetic: python -m venv adds
    pip, and on 3.11 setuptools too, and a scanner cannot tell "ctrlrun needs this" from "the venv
    came with this"
    . The first version removed only pip, passed on 3.12 and went red in CI on 3.11
    with ['PyYAML', 'click', 'setuptools']. The assertion compares by equality, which is why it
    caught a document that would have overstated what a consumer takes on.

Fixed

  • A shared-directory race in the cookbook tests. Two tests ran the same recipe in
    examples/cookbook/<name>/ on different xdist workers; one's rm -f verify-report.json landed
    between the other's write and read, and bash -euo pipefail turned it into a failure with
    nothing wrong in the library. Measured at four concurrent runs: shared directory 2 of 4 fail,
    one copy per run 4 of 4 pass. Each test now runs in its own copy, and the suite stops writing
    into the working tree.

  • Both adapter READMEs failed on copy-paste. Their examples show identity=... as an
    ellipsis, and dropping it leaves the first call refused with no principal is available. Both
    now state the requirement and say why it is fail-closed.

  • adapters/PUBLISHED.toml had gone stale in the other direction. 0.11.0 published both
    adapters at 1.2.0 with <0.12 and never updated the record, so the file spent a release
    claiming 1.1.0 / <0.11. Both adapters go to 1.3.0 with ctrlrun>=0.5,<0.13, because the
    published 1.2.0 excludes this kernel, and RECORDED now freezes 1.2.0's range too.