Skip to content

Commit a61e489

Browse files
committed
docs: AMENDMENT 4, the 1.9.1 red pass closed and one prediction amended
1 parent cfdd471 commit a61e489

1 file changed

Lines changed: 329 additions & 0 deletions

File tree

‎docs/PREDICTIONS_v19_enforcement.md‎

Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1797,3 +1797,332 @@ particular call carries positional arguments the gate cannot name. Both are new
17971797
refusals on paths that previously ran.
17981798

17991799
Everything after this line is append only.
1800+
1801+
---
1802+
1803+
## AMENDMENT 4 (2026-09-10): the 1.9.1 red pass closed, one prediction amended
1804+
1805+
Measured on `v1.9.1-binding-collision` at
1806+
`cfdd471 fix: bind through partials, coerce recipient strings, refuse
1807+
unobservable recipient parameters`, which is the build commit, on top of the freeze commit
1808+
`d1528f6 docs: freeze 1.9.1 red pass, three binding gaps as strict xfails`.
1809+
`/tmp/al18-extras`, `/tmp/al19-mcp1` and `/tmp/al18-probe313` are editable
1810+
installs of this checkout and were measured against the built tree. No merge, no
1811+
tag, no push, no upload. The two restatements recorded in the freeze, T1 and T2,
1812+
are what the build follows; Q1 to Q6 are otherwise unchanged, except where U1
1813+
below amends Z9.
1814+
1815+
### Two things found during the build, recorded before the scoreboard
1816+
1817+
Neither could be recorded in the freeze, because both were found after it was
1818+
committed. Both were findable before it and were missed. They are stated first
1819+
so the scoreboard below is read against them rather than around them.
1820+
1821+
#### U1. Z9 and Q6 leave the README's 1.9.1 test counts false
1822+
1823+
`README.md:317` carried the 1.9.1 versions row and `:340` a sentence in the
1824+
paragraph that footnotes it, both quoting the suite figures 1.9.1 had before this
1825+
red pass:
1826+
1827+
```
1828+
| 1.9.1 | binding collision: ... | 1509 with the `crypto` and `mcp` extras, 9 skipped |
1829+
```
1830+
1831+
```
1832+
tests taking the place of the 1.x one. For 1.9.1 it is 1509 passing and 9
1833+
skipped under `mcp 2.x` and 1508 passing and 10 skipped under `mcp 1.x`, the
1834+
six added tests being the binding collision class ...
1835+
```
1836+
1837+
Eleven tests were added by this red pass, so both figures are now wrong by
1838+
eleven, and the row's highlights column describes one of the four binding rules
1839+
1.9.1 now carries. 1.9.1 is unreleased and those lines were written by this
1840+
branch's own build commit, so they are a claim about the release this branch is
1841+
still shaping, not history.
1842+
1843+
This is the shape recorded as R2 in the release freeze and as S2 in the 1.9.1
1844+
freeze, twice: an edit list narrower than the change requires, leaving a stale
1845+
current claim standing. It is also what the repository's standing rule on version
1846+
and test-count claims exists to prevent.
1847+
1848+
**Amendment, applied before the scoreboard was written:** `README.md` is added to
1849+
Q6's file list and to Z9's, for three edits: the 1.9.1 versions row, the
1850+
per-version count sentence, and the paragraph that scopes which binding rules the
1851+
engine carries and the standalone adapters do not. Z9 is scored against ten paths
1852+
rather than nine. Scoring it as a MISMATCH and stopping would have left the false
1853+
counts in the tree, which is the outcome the prediction existed to prevent.
1854+
1855+
#### U2. XR5 and XR6 asserted a field `AuthResult` does not have
1856+
1857+
The freeze wrote both as:
1858+
1859+
```python
1860+
assert result.decision.value == "deny"
1861+
assert result.reason == "recipient_not_allowed"
1862+
```
1863+
1864+
`AuthResult` carries no `reason`. The denial reason is at
1865+
`result.denial["reason"]`, as `raise_if_denied` at `agentlock/gate.py` reads it.
1866+
Both tests would have raised `AttributeError` against any engine, so at freeze
1867+
they xfailed for two reasons at once and the measurement could not distinguish
1868+
them.
1869+
1870+
The assertions were corrected to the real surface, and the corrected tests were
1871+
then re-measured against the unfixed engine, by stashing the build's changes to
1872+
`agentlock/` and running the two tests alone:
1873+
1874+
```
1875+
$ git stash push -q agentlock/ && python3 -m pytest ...::test_xr5... ...::test_xr6... -q -rx
1876+
XFAIL tests/test_v19_enforcement_gaps.py::TestBindingRedPass::test_xr5_a_lying_str_subclass_asserted_as_the_recipient_denies - P2: the gate reads the overridden methods
1877+
XFAIL tests/test_v19_enforcement_gaps.py::TestBindingRedPass::test_xr6_a_lying_str_subclass_in_the_declared_parameter_denies - P2: the gate reads the overridden methods
1878+
============================== 2 xfailed in 0.05s ==============================
1879+
```
1880+
1881+
Both still fail at 1.9.1 with the corrected assertions, so the freeze's strict
1882+
xfail was justified on the mechanism and not only on the broken assertion. The
1883+
correction is what Z6 is scored against. The corrected form asserts more than the
1884+
freeze's did:
1885+
1886+
```python
1887+
assert result.allowed is False
1888+
assert result.decision.value == "deny"
1889+
assert result.denial is not None
1890+
assert result.denial["reason"] == "recipient_not_allowed"
1891+
```
1892+
1893+
No other test in the class was edited between freeze and build. The only other
1894+
change to the file was removing the seven strict xfail markers.
1895+
1896+
### Scoreboard
1897+
1898+
| Prediction | Verdict | Evidence |
1899+
|---|---|---|
1900+
| Z6 | MATCH | The seven strict xfail markers came off XR1, XR2, XR5, XR6, XR7, XR8 and XR10 and all seven pass. XR3, XR4, XR9 and XR11 carried no marker and pass, at freeze and after the build. No test in the file reports XPASS or failure in any of the four environments. `TestBindingRedPass` is 11 passed, 0 skipped, everywhere; `TestBindingCollision` is 6 passed. XR5 and XR6 are scored on the corrected assertions per U2. |
1901+
| Z7 | MATCH | checkout `1515 passed, 14 skipped, 0 failed`; `/tmp/al18-extras` `1520 passed, 9 skipped, 0 failed`; `/tmp/al19-mcp1` `1519 passed, 10 skipped, 0 failed`. Each is exactly the resolved figure Z7 states, and each is its own pre-class figure plus 11. Suite lines below. |
1902+
| Z8 | MATCH | `mypy agentlock/ --ignore-missing-imports` reports `Success: no issues found in 34 source files`. `ruff check .` reports `All checks passed!`. The legacy-name grep over `agentlock tests schema` returns 0. |
1903+
| Z9 | MATCH, against the amended list | Ten paths across the three commits: the eight of Q6, plus `agentlock/exceptions.py` per T2, plus `README.md` per U1. `git show --stat` for both code commits below. Nothing else. As frozen, Z9 named nine and the build touched ten; U1 states why the tenth is there and was applied before this table was written. |
1904+
| Z10 | MATCH | `rm -rf dist build` then a rebuild in `/tmp/al18-extras`. `twine check` PASSED on both artifacts. Wheel METADATA carries `Metadata-Version: 2.4` and `Version: 1.9.1`. `/tmp/al191b-wheel`, a fresh venv on CPython 3.14.6, installed the wheel and prints 1.9.1. `/tmp/al191b_wheel_repro.py`, written outside the repository and run from `/tmp`, reports `30 passed, 0 failed`: P1 raises `BindingError` plain and nested, P2 denies both routes and allows the subclass whose data is a contact, P3 raises `BindingError` at wrap time and at call time. |
1905+
1906+
Five predictions, five MATCH, 0 MISMATCH. One of the five is scored against an
1907+
amended prediction, and the amendment is U1 above rather than a silent widening
1908+
of the list.
1909+
1910+
### The four suite lines
1911+
1912+
```
1913+
$ python3 -m pytest -q # checkout, CPython 3.14.6, no mcp
1914+
1515 passed, 14 skipped, 25 warnings in 3.10s
1915+
1916+
$ /tmp/al18-extras/bin/python -m pytest -q # CPython 3.14.6, mcp 2.2.0
1917+
1520 passed, 9 skipped, 25 warnings in 3.40s
1918+
1919+
$ /tmp/al19-mcp1/bin/python -m pytest -q # CPython 3.13.14, mcp 1.30.0
1920+
1519 passed, 10 skipped in 2.99s
1921+
1922+
$ /tmp/al18-probe313/bin/python -m pytest -q # CPython 3.13.14, mcp 2.2.0, pyautogen 0.9.0
1923+
1521 passed, 8 skipped in 3.27s
1924+
```
1925+
1926+
`/tmp/al18-probe313` is not scored by Z7 and is recorded because XC3 runs there
1927+
against a real `pyautogen 0.9.0` rather than under the monkeypatched import
1928+
check. Its figure is its own pre-class 1510 plus the same 11.
1929+
1930+
Every environment's delta from its own pre-class figure is exactly 11, and every
1931+
environment's delta from its own freeze figure is exactly the seven xfails
1932+
turning into passes.
1933+
1934+
### Per test, after the build
1935+
1936+
```
1937+
$ /tmp/al18-extras/bin/python -m pytest tests/test_v19_enforcement_gaps.py -q -rs
1938+
SKIPPED [1] tests/test_v19_enforcement_gaps.py:329: needs the mcp 1.x SDK
1939+
================== 25 passed, 1 skipped, 11 warnings in 0.32s ==================
1940+
1941+
$ python3 -m pytest "tests/test_v19_enforcement_gaps.py::TestBindingRedPass" -q
1942+
======================== 11 passed, 6 warnings in 0.01s ========================
1943+
1944+
$ python3 -m pytest "tests/test_v19_enforcement_gaps.py::TestBindingCollision" -q
1945+
======================== 6 passed, 3 warnings in 0.01s =========================
1946+
```
1947+
1948+
### Z8 verbatim
1949+
1950+
```
1951+
$ /tmp/al18-extras/bin/mypy agentlock/ --ignore-missing-imports
1952+
Success: no issues found in 34 source files
1953+
1954+
$ /tmp/al18-extras/bin/ruff check .
1955+
All checks passed!
1956+
```
1957+
1958+
The source file count stays 34: the red pass adds no module.
1959+
1960+
### Z9 verbatim
1961+
1962+
```
1963+
$ git show --stat --name-only --format= d1528f6
1964+
docs/PREDICTIONS_v19_enforcement.md
1965+
tests/test_v19_enforcement_gaps.py
1966+
1967+
$ git show --stat --name-only --format= cfdd471
1968+
CHANGELOG.md
1969+
README.md
1970+
agentlock/binding.py
1971+
agentlock/decorators.py
1972+
agentlock/exceptions.py
1973+
agentlock/gate.py
1974+
agentlock/integrations/autogen.py
1975+
agentlock/policy.py
1976+
tests/test_v19_enforcement_gaps.py
1977+
```
1978+
1979+
Ten distinct paths. `agentlock/token.py`, `agentlock/integrations/mcp.py`,
1980+
`agentlock/__init__.py`, `pyproject.toml`, `CITATION.cff` and `schema/` are not
1981+
touched: the version does not move, no module is added or removed from the
1982+
package's public surface, and the MCP integration binds no Python signature.
1983+
1984+
### T1 and T2 closed
1985+
1986+
T1: the coercion is applied to the asserted `recipient` as well as to each entry
1987+
of `resolved_recipients`, both in the D18 block, so `RequestContext` carries no
1988+
`str` subclass in either of its two recipient fields. XR5 measures the field Q2's
1989+
literal text did not name.
1990+
1991+
T2: `agentlock/exceptions.py` now states all four reasons `BindingError` is
1992+
raised and groups them by moment, two at wrap time and two at call time. The
1993+
1.9.0 and 1.9.1 reasons are unchanged in substance and are described as they
1994+
were.
1995+
1996+
### What the build decided that no decision named
1997+
1998+
Three, all small, all recorded so the record does not imply otherwise.
1999+
2000+
**The wrappers invoke the callable underneath the partial, not the partial.**
2001+
`bind_call_parameters` returns a binding against the function it bound, which is
2002+
the one underneath. Calling the partial with that binding would apply the
2003+
partial's own arguments a second time. `unwrap_partial` is public in
2004+
`agentlock/binding.py` and resolved once at wrap time, because it does not vary
2005+
per call. It is deliberately not added to `agentlock/__init__.py`: Q6 excludes
2006+
that file and no decision asks for a new name on the package's public surface.
2007+
2008+
**Permissions are built before the wrap-time check, and the check runs before the
2009+
tool is registered.** Q3 requires the block's `scope.recipient_parameter`, which
2010+
the decorator did not have at the point where `ensure_bindable` used to be
2011+
called. The build moved the call after the permissions are built and left it
2012+
before `gate.register_tool`, so a refused pair does not leave a registered tool
2013+
behind. `AutoGen`'s `_wrap_function` takes the block as a third argument for the
2014+
same reason.
2015+
2016+
**Decorating a partial still requires an explicit `name`.**
2017+
`agentlock/decorators.py` reads `tool_name = name or func.__name__` and a
2018+
`functools.partial` has no `__name__`. Q1 unwraps inside the binding and says
2019+
nothing about tool naming, so the build changes nothing there and the omission
2020+
raises `AttributeError` at wrap time, which fails closed. This was recorded in
2021+
Section 5 of the freeze before the build and is unchanged by it.
2022+
2023+
### Z10 verbatim
2024+
2025+
```
2026+
$ /tmp/al18-extras/bin/twine check dist/*
2027+
Checking dist/agentlock-1.9.1-py3-none-any.whl: PASSED
2028+
Checking dist/agentlock-1.9.1.tar.gz: PASSED
2029+
```
2030+
2031+
Wheel `agentlock-1.9.1.dist-info/METADATA`, first three lines:
2032+
2033+
```
2034+
Metadata-Version: 2.4
2035+
Name: agentlock
2036+
Version: 1.9.1
2037+
```
2038+
2039+
`pyproject.toml:2` still reads `requires = ["hatchling<1.30"]` and the build
2040+
resolved `hatchling==1.29.0`.
2041+
2042+
Artifacts, `sha256sum dist/*`:
2043+
2044+
```
2045+
6670cb928b63e1e1ea11dac4012074c931f70e87948557ef3f6586866323de0b dist/agentlock-1.9.1-py3-none-any.whl
2046+
a2d35f93e6b8672901ae141704d7c41bb23f6a6e413127ac920fd35560704c83 dist/agentlock-1.9.1.tar.gz
2047+
```
2048+
2049+
These replace the artifacts recorded in AMENDMENT 3, whose wheel was
2050+
`026c785d827c2fd579e5a4e444ee924a5aff36bf50c9f08a64321cfc316e677c`. That wheel is
2051+
the one the red pass was run against and it is superseded: it carries none of the
2052+
three fixes above and must not be uploaded. Neither of the new artifacts is
2053+
uploaded and neither is committed. `dist/` is ignored.
2054+
2055+
The wheel reproduction, run from `/tmp` against a fresh venv on CPython 3.14.6.
2056+
The script guards its own premise and exits before testing anything if the
2057+
resolved `agentlock` package is not under the venv's `purelib`.
2058+
2059+
```
2060+
$ /tmp/al191b-wheel/bin/python /tmp/al191b_wheel_repro.py
2061+
agentlock 1.9.1 from /tmp/al191b-wheel/lib/python3.14/site-packages/agentlock/__init__.py
2062+
2063+
(a) P1, a partial hiding its pre-bound recipient
2064+
PASS partial call raises BindingError
2065+
PASS the error names the colliding key
2066+
PASS the body never ran
2067+
PASS nested partial raises BindingError
2068+
PASS the body still never ran
2069+
PASS a pre-bound keyword reaches the parameters
2070+
PASS the binding reconstructs the call
2071+
2072+
(b) P2, a str subclass lying about what it holds
2073+
PASS asserted recipient denies
2074+
PASS the reason is recipient_not_allowed
2075+
PASS declared parameter denies
2076+
PASS the reason is recipient_not_allowed
2077+
PASS a subclass whose data is a contact is allowed
2078+
2079+
(c) P3, a declared recipient parameter nothing can carry
2080+
PASS wrap time refusal
2081+
PASS the error names the declared parameter
2082+
PASS a **kwargs signature wraps
2083+
PASS call time refusal for an unnamed positional
2084+
PASS the error names the declared parameter
2085+
PASS the body never ran
2086+
PASS the keyword route still denies
2087+
PASS the body still never ran
2088+
PASS a known contact executes
2089+
PASS the counter is 1
2090+
PASS no declaration leaves *args alone
2091+
PASS that body did run
2092+
2093+
(d) the 1.9.0 and 1.9.1 shapes, unchanged
2094+
PASS all three hostile routes deny
2095+
PASS no body ran
2096+
PASS a known contact executes
2097+
PASS the collision rule still refuses
2098+
PASS that body never ran
2099+
PASS a key naming the variadic itself still binds
2100+
2101+
30 passed, 0 failed
2102+
ALL PASS
2103+
```
2104+
2105+
Sections (a) to (c) are what Z10 requires. Section (d) is not, and is there for
2106+
the reason XC6 exists: the four binding rules now live in one function, and a
2107+
rule that closed P1 by breaking G1's fix or 1.9.1's collision rule would be a
2108+
worse release than no rule. The wheel carries all four.
2109+
2110+
### What is left for the manual step
2111+
2112+
1. Merge, tag `v1.9.1`, and push. Nothing here merged, tagged or pushed.
2113+
2. Upload `dist/agentlock-1.9.1-py3-none-any.whl` and
2114+
`dist/agentlock-1.9.1.tar.gz`, whose hashes are recorded above, after the
2115+
push. Nothing here uploaded. The AMENDMENT 3 artifacts are superseded and must
2116+
not be uploaded.
2117+
3. `CITATION.cff` still reads `version: 1.9.1` with `date-released: 2026-09-10`,
2118+
written by the 1.9.1 build commit, and is correct for this release. After
2119+
Zenodo mints the 1.9.1 version DOI from the GitHub release, add it there as a
2120+
second `identifiers` entry and to the README's software archive line, in a
2121+
follow-up docs commit.
2122+
2123+
The standalone adapters are the fourth thing and are not part of this release.
2124+
None of the five carries any of the four binding rules, which is stated in the
2125+
CHANGELOG and the README rather than left for a reader to discover. They are
2126+
fixed in their own repositories, on their own releases.
2127+
2128+
Everything after this line is append only.

0 commit comments

Comments
 (0)