claude-multiacc 1.0.21 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -399
- package/bin/cli.mjs +13 -1
- package/bin/multiacc-select +52 -0
- package/docs/ACCOUNT_OPERATIONS.md +396 -0
- package/docs/UNIFIED_SELECTOR.md +42 -0
- package/lib/__pycache__/selector_policy.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_primitives.cpython-312.pyc +0 -0
- package/lib/selector_policy.py +174 -0
- package/lib/selector_primitives.py +201 -0
- package/package.json +6 -4
- package/tests/test_selector.py +209 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""Strict transport, normalization, and RFC 8785 primitives for pool-selection.v2."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import math
|
|
8
|
+
import re
|
|
9
|
+
import unicodedata
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
from decimal import Decimal, InvalidOperation
|
|
12
|
+
|
|
13
|
+
SCHEMA = "claude-multiacc/pool-selection.v2"
|
|
14
|
+
SELECTOR_VERSION = "2.0.0"
|
|
15
|
+
TIME_RE = re.compile(
|
|
16
|
+
r"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-5][0-9]"
|
|
17
|
+
r"(\.[0-9]{1,6})?(Z|[+-][0-9]{2}:[0-9]{2})$")
|
|
18
|
+
DECIMAL_RE = re.compile(r"^-?(0|[1-9][0-9]*)(\.[0-9]+)?$")
|
|
19
|
+
SAFE_INTEGER = 9_007_199_254_740_991
|
|
20
|
+
MAX_PLAIN_DECIMAL_PLACES = 10_000
|
|
21
|
+
IDENTITY_KEYS = {"runner_id", "runner_generation", "engine", "account_id"}
|
|
22
|
+
CANDIDATE_KEYS = IDENTITY_KEYS | {
|
|
23
|
+
"status", "weekly_pct", "session_pct", "resets_at", "limited_until", "seen_at",
|
|
24
|
+
"provider_capable", "reservation_history"}
|
|
25
|
+
REQUEST_KEYS = {
|
|
26
|
+
"schema", "database_now", "policy", "required_engine", "producer_identity",
|
|
27
|
+
"excluded_identities", "candidates", "reservation_key"}
|
|
28
|
+
POLICIES = {
|
|
29
|
+
"default_claude", "default_codex", "default_both", "explicit", "producer_retry", "reviewer"}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def timestamp(value: object) -> tuple[datetime, str]:
|
|
33
|
+
if not isinstance(value, str) or not TIME_RE.fullmatch(value):
|
|
34
|
+
raise ValueError("invalid timestamp")
|
|
35
|
+
try:
|
|
36
|
+
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
37
|
+
utc = parsed.astimezone(timezone.utc).replace(tzinfo=None)
|
|
38
|
+
except (ValueError, OverflowError) as error:
|
|
39
|
+
raise ValueError("invalid timestamp") from error
|
|
40
|
+
return parsed, utc.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def decimal_value(value: object) -> tuple[Decimal | None, str | None]:
|
|
44
|
+
if isinstance(value, bool) or value is None:
|
|
45
|
+
return None, None
|
|
46
|
+
if isinstance(value, str) and not DECIMAL_RE.fullmatch(value):
|
|
47
|
+
return None, None
|
|
48
|
+
try:
|
|
49
|
+
parsed = Decimal(str(value))
|
|
50
|
+
if not parsed.is_finite() or (isinstance(value, float) and not math.isfinite(value)):
|
|
51
|
+
return None, None
|
|
52
|
+
if parsed <= 0:
|
|
53
|
+
clamped = Decimal(0)
|
|
54
|
+
elif parsed >= 100:
|
|
55
|
+
clamped = Decimal(100)
|
|
56
|
+
elif parsed.as_tuple().exponent < -MAX_PLAIN_DECIMAL_PLACES:
|
|
57
|
+
return None, None
|
|
58
|
+
else:
|
|
59
|
+
clamped = parsed
|
|
60
|
+
result = format(clamped, "f") if clamped else "0"
|
|
61
|
+
except (InvalidOperation, ValueError, OverflowError):
|
|
62
|
+
return None, None
|
|
63
|
+
return clamped, result.rstrip("0").rstrip(".") if "." in result else result
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def identity(raw: dict) -> tuple[int | None, int | None, str | None, str | None, bool]:
|
|
67
|
+
runner = raw.get("runner_id")
|
|
68
|
+
generation = raw.get("runner_generation")
|
|
69
|
+
engine_value = raw.get("engine")
|
|
70
|
+
account_value = raw.get("account_id")
|
|
71
|
+
engine = engine_value.strip().lower() if isinstance(engine_value, str) else None
|
|
72
|
+
account = unicodedata.normalize("NFC", account_value).strip() \
|
|
73
|
+
if isinstance(account_value, str) else None
|
|
74
|
+
runner = runner if isinstance(runner, int) and not isinstance(runner, bool) and runner > 0 else None
|
|
75
|
+
generation = generation if isinstance(generation, int) \
|
|
76
|
+
and not isinstance(generation, bool) and generation > 0 else None
|
|
77
|
+
engine = engine if engine in {"claude", "codex"} else None
|
|
78
|
+
account = account or None
|
|
79
|
+
return runner, generation, engine, account, all((runner, generation, engine, account))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def collision_key(parts: tuple) -> tuple:
|
|
83
|
+
runner, generation, engine, account = parts
|
|
84
|
+
folded = unicodedata.normalize("NFC", account.casefold()) if account else account
|
|
85
|
+
return runner, generation, engine, folded
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _encode_rfc8785(value: object) -> str:
|
|
89
|
+
if value is None:
|
|
90
|
+
return "null"
|
|
91
|
+
if value is True:
|
|
92
|
+
return "true"
|
|
93
|
+
if value is False:
|
|
94
|
+
return "false"
|
|
95
|
+
if isinstance(value, int):
|
|
96
|
+
if abs(value) > SAFE_INTEGER:
|
|
97
|
+
raise ValueError("integer outside RFC 8785 interoperable range")
|
|
98
|
+
return str(value)
|
|
99
|
+
if isinstance(value, str):
|
|
100
|
+
value.encode("utf-8")
|
|
101
|
+
return json.dumps(value, ensure_ascii=False, separators=(",", ":"))
|
|
102
|
+
if isinstance(value, list):
|
|
103
|
+
return "[" + ",".join(_encode_rfc8785(item) for item in value) + "]"
|
|
104
|
+
if isinstance(value, dict):
|
|
105
|
+
if any(not isinstance(key, str) for key in value):
|
|
106
|
+
raise ValueError("RFC 8785 object keys must be strings")
|
|
107
|
+
keys = sorted(value, key=lambda key: key.encode("utf-16-be", "surrogatepass"))
|
|
108
|
+
pairs = (_encode_rfc8785(key) + ":" + _encode_rfc8785(value[key]) for key in keys)
|
|
109
|
+
return "{" + ",".join(pairs) + "}"
|
|
110
|
+
raise ValueError(f"value outside frozen RFC 8785 digest domain: {type(value).__name__}")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def canonical_sha256(value: object) -> str:
|
|
114
|
+
return hashlib.sha256(_encode_rfc8785(value).encode("utf-8")).hexdigest()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _identity_error(value: object, field: str) -> str | None:
|
|
118
|
+
if not isinstance(value, dict) or set(value) != IDENTITY_KEYS:
|
|
119
|
+
return field
|
|
120
|
+
runner, generation, _, account, valid = identity(value)
|
|
121
|
+
if not isinstance(value.get("engine"), str) or not valid:
|
|
122
|
+
return field
|
|
123
|
+
if abs(runner or 0) > SAFE_INTEGER or abs(generation or 0) > SAFE_INTEGER or account is None:
|
|
124
|
+
return field
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _candidate_error(value: object, index: int) -> str | None:
|
|
129
|
+
field = f"candidates[{index}]"
|
|
130
|
+
if not isinstance(value, dict) or set(value) != CANDIDATE_KEYS:
|
|
131
|
+
return field
|
|
132
|
+
for name in ("runner_id", "runner_generation"):
|
|
133
|
+
item = value[name]
|
|
134
|
+
if not isinstance(item, int) or isinstance(item, bool) or not 0 < item <= SAFE_INTEGER:
|
|
135
|
+
return f"{field}.{name}"
|
|
136
|
+
if not all(isinstance(value[name], str) for name in ("engine", "account_id", "status", "seen_at")):
|
|
137
|
+
return field
|
|
138
|
+
if not isinstance(value["provider_capable"], bool):
|
|
139
|
+
return f"{field}.provider_capable"
|
|
140
|
+
scalars = (str, int, float, Decimal, bool, type(None))
|
|
141
|
+
if any(not isinstance(value[name], scalars) for name in ("weekly_pct", "session_pct")):
|
|
142
|
+
return field
|
|
143
|
+
if any(isinstance(value[name], float) and not math.isfinite(value[name])
|
|
144
|
+
for name in ("weekly_pct", "session_pct")):
|
|
145
|
+
return field
|
|
146
|
+
if any(item is not None and not isinstance(item, str)
|
|
147
|
+
for item in (value["resets_at"], value["limited_until"])):
|
|
148
|
+
return field
|
|
149
|
+
history = value["reservation_history"]
|
|
150
|
+
if not isinstance(history, dict) or set(history) != {"active_expires_at", "last_selected_at"}:
|
|
151
|
+
return f"{field}.reservation_history"
|
|
152
|
+
if any(item is not None and not isinstance(item, str) for item in history.values()):
|
|
153
|
+
return f"{field}.reservation_history"
|
|
154
|
+
if history["active_expires_at"] is not None:
|
|
155
|
+
try:
|
|
156
|
+
timestamp(history["active_expires_at"])
|
|
157
|
+
except ValueError:
|
|
158
|
+
return f"{field}.reservation_history.active_expires_at"
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def request_error(request: object) -> tuple[str, dict] | None:
|
|
163
|
+
if not isinstance(request, dict):
|
|
164
|
+
return "invalid_request", {"field": "$"}
|
|
165
|
+
if "schema" not in request:
|
|
166
|
+
return "invalid_request", {"field": "schema"}
|
|
167
|
+
if request.get("schema") != SCHEMA:
|
|
168
|
+
return "unsupported_schema", {"field": "schema"}
|
|
169
|
+
if set(request) != REQUEST_KEYS:
|
|
170
|
+
return "invalid_request", {"field": "$"}
|
|
171
|
+
try:
|
|
172
|
+
timestamp(request["database_now"])
|
|
173
|
+
except ValueError:
|
|
174
|
+
return "invalid_request", {"field": "database_now"}
|
|
175
|
+
policy, required = request["policy"], request["required_engine"]
|
|
176
|
+
if not isinstance(policy, str) or policy not in POLICIES:
|
|
177
|
+
return "invalid_request", {"field": "policy"}
|
|
178
|
+
if required is not None and (not isinstance(required, str) or required not in {"claude", "codex"}):
|
|
179
|
+
return "invalid_request", {"field": "required_engine"}
|
|
180
|
+
needs_engine = policy in {"explicit", "producer_retry", "reviewer"}
|
|
181
|
+
if (needs_engine and required is None) or (not needs_engine and required is not None):
|
|
182
|
+
return "invalid_request", {"field": "required_engine"}
|
|
183
|
+
producer = request["producer_identity"]
|
|
184
|
+
if (policy == "reviewer") != (producer is not None):
|
|
185
|
+
return "invalid_request", {"field": "producer_identity"}
|
|
186
|
+
if producer is not None and (error := _identity_error(producer, "producer_identity")):
|
|
187
|
+
return "invalid_request", {"field": error}
|
|
188
|
+
excluded = request["excluded_identities"]
|
|
189
|
+
if not isinstance(excluded, list):
|
|
190
|
+
return "invalid_request", {"field": "excluded_identities"}
|
|
191
|
+
for index, value in enumerate(excluded):
|
|
192
|
+
if error := _identity_error(value, f"excluded_identities[{index}]"):
|
|
193
|
+
return "invalid_request", {"field": error}
|
|
194
|
+
if not isinstance(request["reservation_key"], str) or not request["reservation_key"].strip():
|
|
195
|
+
return "invalid_request", {"field": "reservation_key"}
|
|
196
|
+
if not isinstance(request["candidates"], list):
|
|
197
|
+
return "invalid_request", {"field": "candidates"}
|
|
198
|
+
for index, value in enumerate(request["candidates"]):
|
|
199
|
+
if error := _candidate_error(value, index):
|
|
200
|
+
return "invalid_request", {"field": error}
|
|
201
|
+
return None
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-multiacc",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Unified Claude Code and OpenAI Codex subscription pooling with quota-aware selection.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"claude-multiacc": "bin/cli.mjs"
|
|
7
|
+
"claude-multiacc": "bin/cli.mjs",
|
|
8
|
+
"multiacc-select": "bin/multiacc-select"
|
|
8
9
|
},
|
|
9
10
|
"files": [
|
|
10
11
|
"bin/",
|
|
@@ -12,6 +13,7 @@
|
|
|
12
13
|
"install.sh",
|
|
13
14
|
"scripts/postinstall.mjs",
|
|
14
15
|
"tests/",
|
|
16
|
+
"docs/",
|
|
15
17
|
"README.md",
|
|
16
18
|
"CLAUDE_ACCS_TASK.md"
|
|
17
19
|
],
|
|
@@ -51,7 +53,7 @@
|
|
|
51
53
|
},
|
|
52
54
|
"scripts": {
|
|
53
55
|
"postinstall": "node scripts/postinstall.mjs",
|
|
54
|
-
"test": "bash tests/run-tests.sh"
|
|
56
|
+
"test": "bash tests/run-tests.sh && python3 tests/test_selector.py"
|
|
55
57
|
},
|
|
56
58
|
"dependencies": {
|
|
57
59
|
"update-notifier": "^7.3.1"
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""Golden and transport tests for pool-selection.v2."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
import unittest
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
12
|
+
sys.path.insert(0, str(ROOT / "lib"))
|
|
13
|
+
|
|
14
|
+
from selector_policy import select # noqa: E402
|
|
15
|
+
from selector_primitives import canonical_sha256 # noqa: E402
|
|
16
|
+
|
|
17
|
+
SCHEMA = "claude-multiacc/pool-selection.v2"
|
|
18
|
+
NOW = "2026-08-25T09:00:00.000000Z"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _candidate(**patch) -> dict:
|
|
22
|
+
row = {
|
|
23
|
+
"runner_id": 3, "runner_generation": 7, "engine": "claude", "account_id": "acct-a",
|
|
24
|
+
"status": "active", "weekly_pct": 20, "session_pct": 40,
|
|
25
|
+
"resets_at": "2026-08-26T09:00:00Z", "limited_until": None,
|
|
26
|
+
"seen_at": "2026-08-25T08:59:00Z", "provider_capable": True,
|
|
27
|
+
"reservation_history": {"active_expires_at": None, "last_selected_at": None}}
|
|
28
|
+
row.update(patch)
|
|
29
|
+
return row
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _request(candidates: list[dict], **patch) -> dict:
|
|
33
|
+
request = {
|
|
34
|
+
"schema": SCHEMA, "database_now": NOW, "policy": "default_both",
|
|
35
|
+
"required_engine": None, "producer_identity": None, "excluded_identities": [],
|
|
36
|
+
"candidates": candidates, "reservation_key": "proof"}
|
|
37
|
+
request.update(patch)
|
|
38
|
+
return request
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class SelectorTests(unittest.TestCase):
|
|
42
|
+
def test_union_proof_vector_is_byte_stable(self):
|
|
43
|
+
request = _request([
|
|
44
|
+
_candidate(engine="claude", account_id="claude-a", weekly_pct=10, session_pct=20,
|
|
45
|
+
seen_at="2026-08-25T08:54:59Z"),
|
|
46
|
+
_candidate(engine="codex", account_id="codex-b", weekly_pct=30, session_pct=40),
|
|
47
|
+
_candidate(engine="both", account_id=" ", weekly_pct=0, session_pct=0),
|
|
48
|
+
], reservation_key="launch-1")
|
|
49
|
+
response = select(request)
|
|
50
|
+
self.assertEqual(response["engine"], "codex")
|
|
51
|
+
self.assertEqual(response["account_id"], "codex-b")
|
|
52
|
+
self.assertEqual(
|
|
53
|
+
response["candidate_snapshot_digest"],
|
|
54
|
+
"e8ef0b4bc7b95e76231ee6da79616d92bd9251c1c991845c8c0524fcbfb6593a")
|
|
55
|
+
self.assertEqual(
|
|
56
|
+
response["selection_digest"],
|
|
57
|
+
"66179083753e967bbe3914692103c1b4a30966a86ecf85cd62e14f3511c78cc3")
|
|
58
|
+
|
|
59
|
+
def test_unicode_and_active_reservation_proof(self):
|
|
60
|
+
busy = {"active_expires_at": "2026-08-25T09:00:00.000001Z", "last_selected_at": None}
|
|
61
|
+
response = select(_request([
|
|
62
|
+
_candidate(engine="claude", account_id="claude-active", reservation_history=busy),
|
|
63
|
+
_candidate(engine="codex", account_id="café-🚀", weekly_pct=25, session_pct=50),
|
|
64
|
+
], policy="explicit", required_engine="codex", reservation_key="launch-unicode"))
|
|
65
|
+
self.assertEqual((response["engine"], response["account_id"]), ("codex", "café-🚀"))
|
|
66
|
+
self.assertEqual(
|
|
67
|
+
response["candidate_snapshot_digest"],
|
|
68
|
+
"29e8e7d1a611f38ee0be43622102b33875e7ff75ff9024aa43d16151575071d4")
|
|
69
|
+
self.assertEqual(
|
|
70
|
+
response["selection_digest"],
|
|
71
|
+
"eaea620082bb0882a12598b8cc46e79090da07be0b307f34f0a68f555a6c9d3e")
|
|
72
|
+
|
|
73
|
+
def test_both_chooses_each_provider_and_survives_provider_loss(self):
|
|
74
|
+
codex = select(_request([
|
|
75
|
+
_candidate(engine="claude", account_id="claude-a", weekly_pct=70, session_pct=60),
|
|
76
|
+
_candidate(engine="codex", account_id="codex-a", weekly_pct=20, session_pct=30)]))
|
|
77
|
+
claude = select(_request([
|
|
78
|
+
_candidate(engine="claude", account_id="claude-a", weekly_pct=10, session_pct=30),
|
|
79
|
+
_candidate(engine="codex", account_id="codex-a", weekly_pct=20, session_pct=80)]))
|
|
80
|
+
only = select(_request([_candidate(engine="codex", account_id="codex-only")]))
|
|
81
|
+
self.assertEqual((codex["engine"], claude["engine"], only["engine"]),
|
|
82
|
+
("codex", "claude", "codex"))
|
|
83
|
+
|
|
84
|
+
def test_known_quota_and_least_recent_selection_win(self):
|
|
85
|
+
known = select(_request([
|
|
86
|
+
_candidate(engine="claude", account_id="unknown", weekly_pct=None, session_pct=None),
|
|
87
|
+
_candidate(engine="codex", account_id="known", weekly_pct=99, session_pct=99)]))
|
|
88
|
+
old = {"active_expires_at": None, "last_selected_at": "2026-08-25T08:00:00Z"}
|
|
89
|
+
recent = {"active_expires_at": None, "last_selected_at": "2026-08-25T08:50:00Z"}
|
|
90
|
+
fair = select(_request([
|
|
91
|
+
_candidate(engine="claude", account_id="recent", weekly_pct=20, session_pct=20,
|
|
92
|
+
reservation_history=recent),
|
|
93
|
+
_candidate(engine="codex", account_id="old", weekly_pct=20, session_pct=20,
|
|
94
|
+
reservation_history=old)]))
|
|
95
|
+
self.assertEqual((known["account_id"], fair["account_id"]), ("known", "old"))
|
|
96
|
+
utc = select(_request([_candidate()]))
|
|
97
|
+
offset = select(_request(
|
|
98
|
+
[_candidate()], database_now="2026-08-25T12:00:00+03:00"))
|
|
99
|
+
self.assertEqual(utc["selection_digest"], offset["selection_digest"])
|
|
100
|
+
|
|
101
|
+
def test_explicit_and_reviewer_policies_never_cross_provider(self):
|
|
102
|
+
explicit = select(_request([
|
|
103
|
+
_candidate(engine="claude", account_id="free", weekly_pct=0, session_pct=0),
|
|
104
|
+
_candidate(engine="codex", account_id="busy", weekly_pct=90, session_pct=90),
|
|
105
|
+
], policy="explicit", required_engine="codex"))
|
|
106
|
+
producer = {"runner_id": 3, "runner_generation": 7, "engine": "claude",
|
|
107
|
+
"account_id": "producer"}
|
|
108
|
+
review = select(_request([
|
|
109
|
+
_candidate(engine="claude", account_id="producer", weekly_pct=0, session_pct=0),
|
|
110
|
+
_candidate(engine="claude", account_id="reviewer", weekly_pct=90, session_pct=90),
|
|
111
|
+
], policy="reviewer", required_engine="claude", producer_identity=producer,
|
|
112
|
+
excluded_identities=[producer]))
|
|
113
|
+
sole = select(_request([
|
|
114
|
+
_candidate(engine="claude", account_id="producer")], policy="reviewer",
|
|
115
|
+
required_engine="claude", producer_identity=producer))
|
|
116
|
+
alternative = {"runner_id": 4, "runner_generation": 7, "engine": "claude",
|
|
117
|
+
"account_id": "alternative"}
|
|
118
|
+
fallback = select(_request([
|
|
119
|
+
_candidate(account_id="producer"),
|
|
120
|
+
_candidate(runner_id=4, account_id="alternative")], policy="reviewer",
|
|
121
|
+
required_engine="claude", producer_identity=producer,
|
|
122
|
+
excluded_identities=[alternative]))
|
|
123
|
+
self.assertEqual((explicit["engine"], review["account_id"]), ("codex", "reviewer"))
|
|
124
|
+
self.assertEqual(sole["fallback_reason"], "sole_eligible_account")
|
|
125
|
+
self.assertEqual((fallback["account_id"], fallback["fallback_reason"]),
|
|
126
|
+
("producer", "sole_eligible_account"))
|
|
127
|
+
|
|
128
|
+
def test_stable_errors_fail_closed(self):
|
|
129
|
+
collisions = [(" Acct-A ", "acct-a"), ("CAFÉ", "cafe\u0301")]
|
|
130
|
+
duplicates = [select(_request([
|
|
131
|
+
_candidate(engine=" CLAUDE ", account_id=left),
|
|
132
|
+
_candidate(engine="claude", account_id=right)])) for left, right in collisions]
|
|
133
|
+
no_candidate = select(_request([
|
|
134
|
+
_candidate(engine="claude")], policy="default_codex"))
|
|
135
|
+
invalid = select({**_request([]), "unexpected": True})
|
|
136
|
+
preserved = select(_request([_candidate(account_id=" Acct-A ")]))
|
|
137
|
+
overflow_now = select(_request(
|
|
138
|
+
[], database_now="9999-12-31T23:59:59-01:00"))
|
|
139
|
+
overflow_seen = select(_request([
|
|
140
|
+
_candidate(seen_at="0001-01-01T00:00:00+01:00")]))
|
|
141
|
+
overflow_history = select(_request([_candidate(reservation_history={
|
|
142
|
+
"active_expires_at": "9999-12-31T23:59:59-01:00", "last_selected_at": None})]))
|
|
143
|
+
self.assertTrue(all(item["error_code"] == "duplicate_candidate_identity"
|
|
144
|
+
for item in duplicates))
|
|
145
|
+
self.assertTrue(all(item["error_detail"] == {"input_ordinals": [0, 1]}
|
|
146
|
+
for item in duplicates))
|
|
147
|
+
self.assertEqual(no_candidate["error_code"], "no_candidate")
|
|
148
|
+
self.assertEqual(preserved["account_id"], "Acct-A")
|
|
149
|
+
self.assertEqual(overflow_now["error_detail"], {"field": "database_now"})
|
|
150
|
+
self.assertEqual(overflow_seen["error_code"], "no_candidate")
|
|
151
|
+
self.assertEqual(overflow_history["error_detail"], {
|
|
152
|
+
"field": "candidates[0].reservation_history.active_expires_at"})
|
|
153
|
+
self.assertEqual(invalid, {"schema": SCHEMA, "selector_version": "2.0.0", "ok": False,
|
|
154
|
+
"error_code": "invalid_request", "error_detail": {"field": "$"}})
|
|
155
|
+
|
|
156
|
+
def test_rfc8785_digest_vectors(self):
|
|
157
|
+
self.assertEqual(
|
|
158
|
+
canonical_sha256({"account_id": "café-🚀", "metadata": {"": "bmp", "😀": "astral"}}),
|
|
159
|
+
"2f33747d98d7c2f3cfc0a660eba492a7652a66bc46b147edeccf4cc941154981")
|
|
160
|
+
with self.assertRaises(ValueError):
|
|
161
|
+
canonical_sha256({"unsafe": 9_007_199_254_740_992})
|
|
162
|
+
|
|
163
|
+
def test_cli_transport_and_version_contract(self):
|
|
164
|
+
binary = ROOT / "bin" / "multiacc-select"
|
|
165
|
+
args = [str(binary), "--request-json", "-", "--response-json", "-"]
|
|
166
|
+
good_payload = json.dumps(_request([_candidate(engine="codex")]), separators=(",", ":"))
|
|
167
|
+
good = subprocess.run(args, input=good_payload, text=True, capture_output=True, check=True)
|
|
168
|
+
bad_payloads = [
|
|
169
|
+
good_payload.replace('"weekly_pct":20', '"weekly_pct":NaN', 1),
|
|
170
|
+
good_payload.replace(
|
|
171
|
+
'"weekly_pct":20', '"weekly_pct":1e999999999999999999999999999999', 1),
|
|
172
|
+
good_payload.replace('"policy":"default_both"',
|
|
173
|
+
'"policy":"explicit","policy":"default_both"', 1),
|
|
174
|
+
good_payload.replace('"active_expires_at":null',
|
|
175
|
+
'"active_expires_at":null,"active_expires_at":null', 1),
|
|
176
|
+
good_payload + "{}",
|
|
177
|
+
]
|
|
178
|
+
bad = [subprocess.run(args, input=payload, text=True, capture_output=True, check=True)
|
|
179
|
+
for payload in bad_payloads]
|
|
180
|
+
decimal_request = _request([
|
|
181
|
+
_candidate(account_id="z-lower", weekly_pct="LOW"),
|
|
182
|
+
_candidate(runner_id=4, account_id="a-higher", weekly_pct="HIGH")])
|
|
183
|
+
decimal_payload = json.dumps(decimal_request, separators=(",", ":"))
|
|
184
|
+
decimal_payload = decimal_payload.replace(
|
|
185
|
+
'"LOW"', "50.00000000000000000000000000001")
|
|
186
|
+
decimal_payload = decimal_payload.replace(
|
|
187
|
+
'"HIGH"', "50.00000000000000000000000000002")
|
|
188
|
+
decimal_response = subprocess.run(
|
|
189
|
+
args, input=decimal_payload, text=True, capture_output=True, check=True)
|
|
190
|
+
version = subprocess.run([str(binary), "--version"], text=True,
|
|
191
|
+
capture_output=True, check=True).stdout.strip()
|
|
192
|
+
version_script = (
|
|
193
|
+
"import {resolveVersion} from './scripts/auto-version.mjs';"
|
|
194
|
+
"console.log(resolveVersion('2.0.0','1.0.21'));"
|
|
195
|
+
"console.log(resolveVersion('2.0.0','2.0.0'));"
|
|
196
|
+
)
|
|
197
|
+
versions = subprocess.run(
|
|
198
|
+
["node", "--input-type=module", "-e", version_script], cwd=ROOT,
|
|
199
|
+
text=True, capture_output=True, check=True).stdout.splitlines()
|
|
200
|
+
self.assertTrue(json.loads(good.stdout)["ok"])
|
|
201
|
+
self.assertTrue(all(json.loads(item.stdout)["error_code"] == "invalid_request"
|
|
202
|
+
for item in bad))
|
|
203
|
+
self.assertEqual(json.loads(decimal_response.stdout)["account_id"], "z-lower")
|
|
204
|
+
self.assertEqual(version, "2.0.0")
|
|
205
|
+
self.assertEqual(versions, ["2.0.0", "2.0.1"])
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
if __name__ == "__main__":
|
|
209
|
+
unittest.main()
|