Advance the managed Claudexor runtime pin to 3.6.0 (unified accounts)

The pin moves 3.5.0 -> 3.6.0, the release whose headline is the unified
account model (INV-135 rewrite): every account is a named registry row and
the pool-authority read GET /v2/account-pools is the feature marker. Pin
provenance, verified against the frozen release candidate (claudexor
31aa51c9, candidate release run 32148933151; A-5 exact-artifact promotion
ships these bytes verbatim at publish): archive
https://github.com/razzant/claudexor/releases/download/v3.6.0/claudexor-runtime-3.6.0.tar.gz,
sha256 72b0936eb8825ee1da6adae0dfd8720a8c31c875dce4fb7cb0e36534f279918a
(matches the candidate's runtime-manifest.json AND SHA256SUMS AND an
independent shasum of the artifact bytes), size 20887532; the extracted
closure's identity probe reports {"version":"3.6.0","buildSha":"31aa51c9..."}
== the pin. protocol_major stays 3, Node stays 24.16.0, node_artifacts and
entrypoint untouched. One post-publish step remains: the archive_url goes
live when the owner publishes v3.6.0 -- verify with
scripts/fetch_claudexor_runtime.py --output-dir <tmp> before pushing.

The cross-repo marker byte-assertion (the sprint's recorded obligation):
test_pinned_engine_serves_the_account_pools_marker_id reads the tracked pin
and, for a >= 3.6 pinned engine, asserts the Ouroboros-side feature-detect
literal _ACCOUNT_POOLS_OPERATION_ID == "get:account-pools" byte-for-byte,
plus a 3.6-shaped catalog fixture (the accounts-surface rows exactly as the
engine generates them) with and without the marker row. Claudexor pins the
same literal from its side in control-api.test.ts; a respelling in either
repo now goes red instead of silently degrading every install to the legacy
accounts rendering.

Issue #246 (_ROTATION_AUTO_SEMANTICS_MIN_VERSION): the kind-aware
limit-action "auto" default (claudexor#197) is confirmed shipped in the
actual 3.6.0 release -- the candidate's schema carries the "auto" enum with
kind-aware semantics -- so the constant already names the real release wave
and stays "3.6.0"; its comment now records the confirmation instead of a
plan.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
This commit is contained in:
Ouroboros 2026-08-18 19:06:22 +03:00
parent 2d77583a2a
commit c58453bb2b
3 changed files with 55 additions and 7 deletions

View file

@ -55,8 +55,10 @@ _ADMISSION_POLL_SEC = 0.15
# (kind-aware "auto" semantics, Clawdexor A6): subscription profiles rotate,
# metered API keys fail, and the OWNER's explicit choices always win. Blanket
# "rotate" writes from this side would overwrite that judgment, so reconcile
# skips those engines entirely. A6 is planned for the 3.6.0 release wave; this
# is deliberately not CLAUDEXOR_MIN_VERSION (owner decision 5=A: no floor bump).
# skips those engines entirely. Confirmed shipped in the actual 3.6.0 release
# (claudexor 31aa51c9, schema limit_action enum carries kind-aware "auto"), so
# this floor names the real release wave (issue #246); it is deliberately not
# CLAUDEXOR_MIN_VERSION (owner decision 5=A: no floor bump).
_ROTATION_AUTO_SEMANTICS_MIN_VERSION = "3.6.0"
_ROTATION_RECEIPT_NAME = "claudexor_rotation_provisioning.json"

View file

@ -1,12 +1,12 @@
{
"schema_version": 1,
"release": {
"version": "3.5.0",
"build_sha": "efff2f3b4f9f8e844fbee3578e8681277871f461",
"version": "3.6.0",
"build_sha": "31aa51c9e545eb061d9335b600a372f484d3f7a9",
"protocol_major": 3,
"archive_url": "https://github.com/razzant/claudexor/releases/download/v3.5.0/claudexor-runtime-3.5.0.tar.gz",
"sha256": "3aa422cc283b0c6930b2f420d77c7865ab74302ba7d24b9d610d0317469dfa16",
"size_bytes": 20818642,
"archive_url": "https://github.com/razzant/claudexor/releases/download/v3.6.0/claudexor-runtime-3.6.0.tar.gz",
"sha256": "72b0936eb8825ee1da6adae0dfd8720a8c31c875dce4fb7cb0e36534f279918a",
"size_bytes": 20887532,
"node_version": "24.16.0",
"node_artifacts": {
"darwin-arm64": {

View file

@ -489,6 +489,52 @@ def test_unified_accounts_capability_reads_the_operations_catalog():
assert _unified_accounts_native([None, "get:account-pools"]) is False
def test_pinned_engine_serves_the_account_pools_marker_id():
"""Cross-repo byte-assertion (unified-accounts sprint obligation): from
Claudexor 3.6.0 the engine's /v2/operations catalog serves the pool-
authority read under the EXACT id `get:account-pools`. The engine derives
ids from routes (`method.toLowerCase() + ':' + path minus its '/v2/'
prefix, [:/<>]+ folded to '.'), and claudexor pins the same literal from
its side (control-api.test.ts asserts the catalog row for
/v2/account-pools carries this id verbatim). If either repo respells it,
the feature detect quietly answers False and every install degrades to
the legacy accounts rendering the deliberate cheap direction of
`_unified_accounts_native`, which is exactly why no behavioral test would
notice. The assertion is gated on the tracked runtime pin so a deliberate
pre-3.6 pin rollback leaves it dormant instead of red."""
from ouroboros.claudexor_runtime import load_runtime_pin
from ouroboros.gateway.claudexor_accounts import (
_ACCOUNT_POOLS_OPERATION_ID,
_unified_accounts_native,
)
pin = load_runtime_pin()
assert pin is not None, "the tracked runtime pin must select a release"
major, minor, _patch = (int(part) for part in pin.version.split("."))
if (major, minor) < (3, 6):
pytest.skip(
f"pinned engine {pin.version} predates the unified account model"
)
assert _ACCOUNT_POOLS_OPERATION_ID == "get:account-pools"
# A 3.6-shaped catalog slice — the accounts-surface rows exactly as the
# pinned engine generates them — satisfies the feature detect...
catalog_3_6 = [
{"id": "get:quota", "method": "GET", "path": "/v2/quota"},
{"id": "get:account-pools", "method": "GET", "path": "/v2/account-pools"},
{"id": "get:credential-profiles", "method": "GET",
"path": "/v2/credential-profiles"},
{"id": "post:accounts-migration.rollback", "method": "POST",
"path": "/v2/accounts-migration/rollback"},
]
assert _unified_accounts_native(catalog_3_6) is True
# ...and the same catalog without the one marker row is the legacy model:
# no neighbouring accounts route may stand in for the marker.
without_marker = [
op for op in catalog_3_6 if op["id"] != _ACCOUNT_POOLS_OPERATION_ID
]
assert _unified_accounts_native(without_marker) is False
def test_status_payload_stamps_the_unified_accounts_fact(monkeypatch, tmp_path):
"""`unified_accounts` rides every status answer: True only when the
operations catalog was READ and carries the account-pools marker; an old