claude-dev-env 8.43.5 → 8.44.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.
Files changed (36) hide show
  1. package/_shared/advisor/advisor-protocol.md +2 -2
  2. package/_shared/advisor/reference/cli-chain.md +9 -12
  3. package/_shared/advisor/reference/lifecycle.md +1 -1
  4. package/_shared/advisor/reference/third-party-bind.md +3 -3
  5. package/bin/install.test.mjs +1 -1
  6. package/docs/second-claude-account.md +11 -13
  7. package/hooks/blocking/reply_length_gate.py +43 -11
  8. package/hooks/blocking/test_reply_length_gate.py +55 -0
  9. package/hooks/hooks.json +1 -1
  10. package/hooks/hooks_constants/reply_length_gate_constants.py +11 -0
  11. package/package.json +1 -1
  12. package/scripts/_code_review_test_support.py +29 -39
  13. package/scripts/claude_account_worker.py +46 -297
  14. package/scripts/claude_account_worker_process.py +18 -154
  15. package/scripts/claude_account_worker_report.py +8 -4
  16. package/scripts/claude_chain_usage.py +7 -271
  17. package/scripts/codec_forwarding_test_support.py +11 -65
  18. package/scripts/dev_env_scripts_constants/claude_account_worker_constants.py +6 -24
  19. package/scripts/invoke_code_review.py +34 -56
  20. package/scripts/resolve_worker_spawn.py +40 -72
  21. package/scripts/test_account_broker_guard.py +0 -4
  22. package/scripts/test_claude_account_worker.py +105 -350
  23. package/scripts/test_claude_chain_usage.py +50 -510
  24. package/scripts/test_dispatcher_profile_import.py +5 -9
  25. package/scripts/test_invoke_code_review.py +7 -6
  26. package/scripts/test_invoke_code_review_chain.py +26 -0
  27. package/scripts/test_invoke_code_review_cli.py +8 -8
  28. package/scripts/test_invoke_code_review_codec.py +3 -11
  29. package/scripts/test_invoke_code_review_contract.py +5 -1
  30. package/scripts/test_resolve_worker_spawn.py +118 -265
  31. package/scripts/test_resolve_worker_spawn_codec.py +9 -21
  32. package/scripts/tests/test_code_review_constants.py +2 -2
  33. package/scripts/claude_account_choice.py +0 -422
  34. package/scripts/claude_chain_runner.py +0 -1289
  35. package/scripts/test_claude_account_choice.py +0 -355
  36. package/scripts/test_claude_chain_runner.py +0 -1839
@@ -1,568 +1,107 @@
1
- """Behavioral tests for the claude chain weekly-usage report tool."""
1
+ """Tests for Claude meter probes used by the account broker."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import json
6
5
  import subprocess
7
6
  import sys
8
- from datetime import datetime
7
+ import urllib.error
8
+ from datetime import datetime, timezone
9
9
  from pathlib import Path
10
10
 
11
11
  import pytest
12
12
 
13
- _SCRIPTS_DIR = Path(__file__).resolve().parent
14
- if str(_SCRIPTS_DIR) not in sys.path:
15
- sys.path.insert(0, str(_SCRIPTS_DIR))
16
-
17
- import claude_chain_runner as chain_runner # noqa: E402
18
- import claude_chain_usage as usage # noqa: E402
19
- from dev_env_scripts_constants.claude_chain_constants import ( # noqa: E402
20
- CHAIN_CONFIG_ERROR_EXIT_CODE,
21
- CONFIG_CHAIN_KEY,
22
- CONFIG_COMMAND_KEY,
23
- CONFIG_CREDENTIALS_PATH_KEY,
24
- CONFIG_EXTRA_ARGS_KEY,
25
- CONFIG_FILENAME,
26
- UTF8_ENCODING,
27
- )
28
- from dev_env_scripts_constants.claude_chain_usage_constants import ( # noqa: E402
29
- CLI_CONFIG_PATH_FLAG,
30
- FULL_WEEKLY_PERCENT,
31
- JSON_ACCOUNTS_KEY,
32
- JSON_COMMAND_KEY,
33
- JSON_ERROR_KEY,
34
- JSON_WEEKLY_REMAINING_PERCENT_KEY,
35
- RESOLVE_USAGE_WINDOW_MODULE_NAME,
36
- )
37
-
38
- PLACEHOLDER_CREDENTIALS_PRIMARY = "/path/to/account-primary/.credentials.json"
39
- PLACEHOLDER_CREDENTIALS_SECONDARY = "/path/to/account-secondary/.credentials.json"
40
- PLACEHOLDER_CREDENTIALS_TERTIARY = "/path/to/account-tertiary/.credentials.json"
41
-
42
-
43
- def _entry(
44
- command: str,
45
- *,
46
- extra_args: list[str] | None = None,
47
- credentials_path: str | None = None,
48
- ) -> dict[str, object]:
49
- chain_entry: dict[str, object] = {
50
- CONFIG_COMMAND_KEY: command,
51
- CONFIG_EXTRA_ARGS_KEY: extra_args if extra_args is not None else [],
52
- }
53
- if credentials_path is not None:
54
- chain_entry[CONFIG_CREDENTIALS_PATH_KEY] = credentials_path
55
- return chain_entry
56
-
57
-
58
- def _write_chain_config(
59
- tmp_path: Path, all_chain_entries: list[dict[str, object]]
60
- ) -> Path:
61
- config_file = tmp_path / CONFIG_FILENAME
62
- config_file.write_text(
63
- json.dumps({CONFIG_CHAIN_KEY: all_chain_entries}), encoding=UTF8_ENCODING
64
- )
65
- return config_file
66
-
67
-
68
- def _probe_from_utilization_by_path(
69
- utilization_by_path: dict[str, float | Exception],
70
- ) -> usage.WeeklyUtilizationProbe:
71
- def active_probe(credentials_path: Path) -> float:
72
- path_key = credentials_path.as_posix()
73
- probe_outcome = utilization_by_path[path_key]
74
- if isinstance(probe_outcome, Exception):
75
- raise probe_outcome
76
- return probe_outcome
77
-
78
- return active_probe
79
-
80
-
81
- def test_remaining_percent_is_full_scale_minus_utilization(
82
- tmp_path: Path, monkeypatch: pytest.MonkeyPatch
83
- ) -> None:
84
- config_file = _write_chain_config(
85
- tmp_path,
86
- [
87
- _entry("claude", credentials_path=PLACEHOLDER_CREDENTIALS_PRIMARY),
88
- _entry("claude-profile-c", credentials_path=PLACEHOLDER_CREDENTIALS_SECONDARY),
89
- ],
90
- )
91
- monkeypatch.setattr(
92
- usage,
93
- "weekly_utilization_probe",
94
- _probe_from_utilization_by_path(
95
- {
96
- PLACEHOLDER_CREDENTIALS_PRIMARY: 42.0,
97
- PLACEHOLDER_CREDENTIALS_SECONDARY: 9.0,
98
- }
99
- ),
100
- )
101
- all_reports = usage.report_chain_weekly_usage(config_path=config_file)
102
- assert [each_report.command for each_report in all_reports] == [
103
- "claude",
104
- "claude-profile-c",
105
- ]
106
- assert all_reports[0].weekly_remaining_percent == pytest.approx(
107
- FULL_WEEKLY_PERCENT - 42.0
108
- )
109
- assert all_reports[1].weekly_remaining_percent == pytest.approx(
110
- FULL_WEEKLY_PERCENT - 9.0
111
- )
112
- assert all_reports[0].error is None
113
- assert all_reports[1].error is None
114
-
115
-
116
- def test_rank_orders_by_remaining_desc_preserves_ties_and_puts_nulls_last() -> None:
117
- all_reports = [
118
- usage.AccountUsageReport(command="first", weekly_remaining_percent=40.0),
119
- usage.AccountUsageReport(command="second", weekly_remaining_percent=70.0),
120
- usage.AccountUsageReport(
121
- command="third", weekly_remaining_percent=None, error="probe failed"
122
- ),
123
- usage.AccountUsageReport(command="fourth", weekly_remaining_percent=70.0),
124
- usage.AccountUsageReport(
125
- command="fifth", weekly_remaining_percent=None, error="no token"
126
- ),
127
- usage.AccountUsageReport(command="sixth", weekly_remaining_percent=10.0),
128
- ]
129
- ranked_reports = usage.rank_accounts_by_weekly_remaining(all_reports)
130
- assert [each_report.command for each_report in ranked_reports] == [
131
- "second",
132
- "fourth",
133
- "first",
134
- "sixth",
135
- "third",
136
- "fifth",
137
- ]
138
-
139
-
140
- def test_probe_failure_yields_null_remaining_and_error_string(
141
- tmp_path: Path, monkeypatch: pytest.MonkeyPatch
142
- ) -> None:
143
- config_file = _write_chain_config(
144
- tmp_path,
145
- [
146
- _entry("claude", credentials_path=PLACEHOLDER_CREDENTIALS_PRIMARY),
147
- _entry("claude-profile-c", credentials_path=PLACEHOLDER_CREDENTIALS_SECONDARY),
148
- ],
149
- )
150
- monkeypatch.setattr(
151
- usage,
152
- "weekly_utilization_probe",
153
- _probe_from_utilization_by_path(
154
- {
155
- PLACEHOLDER_CREDENTIALS_PRIMARY: usage.WeeklyUtilizationProbeError(
156
- "token expired"
157
- ),
158
- PLACEHOLDER_CREDENTIALS_SECONDARY: 25.0,
159
- }
160
- ),
161
- )
162
- all_reports = usage.report_chain_weekly_usage(config_path=config_file)
163
- assert all_reports[0].weekly_remaining_percent is None
164
- assert all_reports[0].error == "token expired"
165
- assert all_reports[1].weekly_remaining_percent == pytest.approx(
166
- FULL_WEEKLY_PERCENT - 25.0
167
- )
168
- assert all_reports[1].error is None
169
-
170
-
171
- def test_missing_config_raises_chain_configuration_error(tmp_path: Path) -> None:
172
- missing_config = tmp_path / "absent-chain.json"
173
- with pytest.raises(chain_runner.ChainConfigurationError):
174
- usage.report_chain_weekly_usage(config_path=missing_config)
175
-
176
-
177
- def test_empty_config_raises_chain_configuration_error(tmp_path: Path) -> None:
178
- config_file = _write_chain_config(tmp_path, [])
179
- with pytest.raises(chain_runner.ChainConfigurationError):
180
- usage.report_chain_weekly_usage(config_path=config_file)
181
-
13
+ import claude_chain_usage as usage
14
+ from dev_env_scripts_constants.claude_chain_usage_constants import RESOLVE_USAGE_WINDOW_MODULE_NAME
182
15
 
183
- def test_entry_without_credentials_path_uses_default(
184
- tmp_path: Path, monkeypatch: pytest.MonkeyPatch
185
- ) -> None:
186
- default_path = Path("/path/to/default/.credentials.json")
187
- monkeypatch.setattr(usage, "_default_credentials_path", lambda: default_path)
188
- config_file = _write_chain_config(tmp_path, [_entry("claude")])
189
- probed_paths: list[Path] = []
190
-
191
- def active_probe(credentials_path: Path) -> float:
192
- probed_paths.append(credentials_path)
193
- return 10.0
194
-
195
- monkeypatch.setattr(usage, "weekly_utilization_probe", active_probe)
196
- all_reports = usage.report_chain_weekly_usage(config_path=config_file)
197
- assert probed_paths == [default_path]
198
- assert all_reports[0].weekly_remaining_percent == pytest.approx(
199
- FULL_WEEKLY_PERCENT - 10.0
200
- )
201
-
202
-
203
- def test_default_credentials_resolution_failure_yields_null_remaining(
204
- tmp_path: Path, monkeypatch: pytest.MonkeyPatch
205
- ) -> None:
206
- def raise_probe_error() -> Path:
207
- raise usage.WeeklyUtilizationProbeError("probe module not found")
208
-
209
- monkeypatch.setattr(usage, "_default_credentials_path", raise_probe_error)
210
- config_file = _write_chain_config(tmp_path, [_entry("claude")])
211
- all_reports = usage.report_chain_weekly_usage(config_path=config_file)
212
- assert all_reports[0].weekly_remaining_percent is None
213
- assert all_reports[0].error == "probe module not found"
214
-
215
-
216
- def test_entry_credentials_path_is_passed_to_probe(
217
- tmp_path: Path, monkeypatch: pytest.MonkeyPatch
218
- ) -> None:
219
- config_file = _write_chain_config(
220
- tmp_path,
221
- [_entry("claude-profile-c", credentials_path=PLACEHOLDER_CREDENTIALS_TERTIARY)],
222
- )
223
- probed_paths: list[Path] = []
224
16
 
225
- def active_probe(credentials_path: Path) -> float:
226
- probed_paths.append(credentials_path)
227
- return 0.0
17
+ def test_probe_reads_both_meters_and_resets(monkeypatch: pytest.MonkeyPatch) -> None:
18
+ session_reset = datetime(2026, 10, 4, tzinfo=timezone.utc)
19
+ weekly_reset = datetime(2026, 10, 7, tzinfo=timezone.utc)
228
20
 
229
- monkeypatch.setattr(usage, "weekly_utilization_probe", active_probe)
230
- usage.report_chain_weekly_usage(config_path=config_file)
231
- assert probed_paths == [Path(PLACEHOLDER_CREDENTIALS_TERTIARY)]
232
-
233
-
234
- def test_entry_credentials_path_expands_user_home(
235
- tmp_path: Path, monkeypatch: pytest.MonkeyPatch
236
- ) -> None:
237
- tilde_credentials_path = "~/.claude-accounts/secondary/.credentials.json"
238
- config_file = _write_chain_config(
239
- tmp_path,
240
- [_entry("claude-profile-c", credentials_path=tilde_credentials_path)],
241
- )
242
- probed_paths: list[Path] = []
243
-
244
- def active_probe(credentials_path: Path) -> float:
245
- probed_paths.append(credentials_path)
246
- return 0.0
247
-
248
- monkeypatch.setattr(usage, "weekly_utilization_probe", active_probe)
249
- usage.report_chain_weekly_usage(config_path=config_file)
250
- assert probed_paths == [Path(tilde_credentials_path).expanduser()]
251
-
252
-
253
- def test_load_chain_carries_optional_credentials_path(tmp_path: Path) -> None:
254
- config_file = _write_chain_config(
255
- tmp_path,
256
- [
257
- _entry("claude"),
258
- _entry("claude-profile-c", credentials_path=PLACEHOLDER_CREDENTIALS_SECONDARY),
259
- ],
260
- )
261
- all_entries = chain_runner.load_chain(config_file)
262
- assert all_entries[0].credentials_path is None
263
- assert all_entries[1].credentials_path == PLACEHOLDER_CREDENTIALS_SECONDARY
264
-
265
-
266
- def test_invalid_credentials_path_type_raises(tmp_path: Path) -> None:
267
- config_file = tmp_path / CONFIG_FILENAME
268
- config_file.write_text(
269
- json.dumps(
270
- {
271
- CONFIG_CHAIN_KEY: [
272
- {
273
- CONFIG_COMMAND_KEY: "claude",
274
- CONFIG_EXTRA_ARGS_KEY: [],
275
- CONFIG_CREDENTIALS_PATH_KEY: 12,
276
- }
277
- ]
278
- }
279
- ),
280
- encoding=UTF8_ENCODING,
281
- )
282
- with pytest.raises(chain_runner.ChainConfigurationError):
283
- chain_runner.load_chain(config_file)
284
-
285
-
286
- def test_reports_to_json_payload_matches_cli_contract() -> None:
287
- all_reports = [
288
- usage.AccountUsageReport(command="claude", weekly_remaining_percent=58.0),
289
- usage.AccountUsageReport(
290
- command="claude-profile-c",
291
- weekly_remaining_percent=None,
292
- error="probe failed",
293
- ),
294
- ]
295
- payload = usage.reports_to_json_payload(all_reports)
296
- assert payload == {
297
- JSON_ACCOUNTS_KEY: [
298
- {
299
- JSON_COMMAND_KEY: "claude",
300
- JSON_WEEKLY_REMAINING_PERCENT_KEY: 58.0,
301
- },
302
- {
303
- JSON_COMMAND_KEY: "claude-profile-c",
304
- JSON_WEEKLY_REMAINING_PERCENT_KEY: None,
305
- JSON_ERROR_KEY: "probe failed",
306
- },
307
- ]
308
- }
309
-
310
-
311
- def test_cli_writes_json_accounts_report(
312
- tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
313
- ) -> None:
314
- config_file = _write_chain_config(
315
- tmp_path,
316
- [
317
- _entry("claude", credentials_path=PLACEHOLDER_CREDENTIALS_PRIMARY),
318
- _entry("claude-profile-c", credentials_path=PLACEHOLDER_CREDENTIALS_SECONDARY),
319
- ],
320
- )
321
- monkeypatch.setattr(
322
- usage,
323
- "weekly_utilization_probe",
324
- _probe_from_utilization_by_path(
325
- {
326
- PLACEHOLDER_CREDENTIALS_PRIMARY: 42.0,
327
- PLACEHOLDER_CREDENTIALS_SECONDARY: usage.WeeklyUtilizationProbeError(
328
- "no token"
329
- ),
330
- }
331
- ),
332
- )
333
- exit_code = usage.main([CLI_CONFIG_PATH_FLAG, str(config_file)])
334
- captured = capsys.readouterr()
335
- assert exit_code == 0
336
- assert captured.err == ""
337
- payload = json.loads(captured.out)
338
- assert payload == {
339
- JSON_ACCOUNTS_KEY: [
340
- {
341
- JSON_COMMAND_KEY: "claude",
342
- JSON_WEEKLY_REMAINING_PERCENT_KEY: FULL_WEEKLY_PERCENT - 42.0,
343
- },
344
- {
345
- JSON_COMMAND_KEY: "claude-profile-c",
346
- JSON_WEEKLY_REMAINING_PERCENT_KEY: None,
347
- JSON_ERROR_KEY: "no token",
348
- },
349
- ]
350
- }
351
-
352
-
353
- def test_cli_missing_config_prints_stderr_and_returns_config_error(
354
- tmp_path: Path, capsys: pytest.CaptureFixture[str]
355
- ) -> None:
356
- missing_config = tmp_path / "absent-chain.json"
357
- exit_code = usage.main([CLI_CONFIG_PATH_FLAG, str(missing_config)])
358
- captured = capsys.readouterr()
359
- assert exit_code == CHAIN_CONFIG_ERROR_EXIT_CODE
360
- assert captured.out == ""
361
- assert "not found" in captured.err.lower() or "claude chain" in captured.err.lower()
362
-
363
-
364
- def test_weekly_remaining_from_utilization_uses_full_scale() -> None:
365
- assert usage.weekly_remaining_from_utilization(9.0) == pytest.approx(91.0)
366
- assert usage.weekly_remaining_from_utilization(0.0) == pytest.approx(
367
- FULL_WEEKLY_PERCENT
368
- )
369
- assert usage.weekly_remaining_from_utilization(100.0) == pytest.approx(0.0)
370
-
371
-
372
- def test_probe_weekly_utilization_reuses_resolver_windows(
373
- monkeypatch: pytest.MonkeyPatch,
374
- ) -> None:
375
- class _FakeWindows:
376
- session_utilization = None
377
- session_resets_at = None
378
- weekly_utilization = 37.5
379
- weekly_resets_at = None
380
-
381
- class _FakeResolver:
382
- def read_oauth_access_token(self, credentials_path: Path, now: object) -> str:
383
- assert credentials_path == Path(PLACEHOLDER_CREDENTIALS_PRIMARY)
384
- return "token-value"
385
-
386
- def resolve_access_token(self, credentials_path: Path, now: object) -> str:
387
- raise AssertionError("probe must not fall back to session ingress")
388
-
389
- def _fetch_usage_payload(self, access_token: str) -> dict[str, object]:
390
- assert access_token == "token-value"
391
- return {"seven_day": {"utilization": 37.5}}
392
-
393
- def extract_usage_windows(
394
- self, usage_payload: dict[str, object]
395
- ) -> _FakeWindows:
396
- assert usage_payload == {"seven_day": {"utilization": 37.5}}
397
- return _FakeWindows()
398
-
399
- monkeypatch.setattr(
400
- usage, "_load_resolve_usage_window_module", lambda: _FakeResolver()
401
- )
402
- weekly_utilization = usage._probe_weekly_utilization(
403
- Path(PLACEHOLDER_CREDENTIALS_PRIMARY)
404
- )
405
- assert weekly_utilization == pytest.approx(37.5)
406
-
407
-
408
- def should_read_both_meters_and_their_resets_for_one_account(
409
- monkeypatch: pytest.MonkeyPatch,
410
- ) -> None:
411
- session_reset = datetime(2026, 9, 22, 23, 0).astimezone()
412
- weekly_reset = datetime(2026, 9, 26, 9, 0).astimezone()
413
-
414
- class _FakeWindows:
21
+ class Windows:
415
22
  session_utilization = 12.0
416
23
  session_resets_at = session_reset
417
24
  weekly_utilization = 64.0
418
25
  weekly_resets_at = weekly_reset
419
26
 
420
- class _FakeResolver:
421
- def read_oauth_access_token(self, credentials_path: Path, now: object) -> str:
422
- return "token-value"
27
+ class Resolver:
28
+ def read_oauth_access_token(self, credentials_path: Path, now: datetime) -> str:
29
+ assert credentials_path == Path("account.json")
30
+ return "fixture-token"
423
31
 
424
32
  def _fetch_usage_payload(self, access_token: str) -> dict[str, object]:
425
- return {}
33
+ assert access_token == "fixture-token"
34
+ return {"usage": 64.0}
426
35
 
427
- def extract_usage_windows(
428
- self, usage_payload: dict[str, object]
429
- ) -> _FakeWindows:
430
- return _FakeWindows()
36
+ def extract_usage_windows(self, payload: dict[str, object]) -> Windows:
37
+ assert payload == {"usage": 64.0}
38
+ return Windows()
431
39
 
432
- monkeypatch.setattr(
433
- usage, "_load_resolve_usage_window_module", lambda: _FakeResolver()
434
- )
435
- account_meters = usage.probe_account_meters(Path(PLACEHOLDER_CREDENTIALS_PRIMARY))
436
- assert account_meters == usage.AccountUsageMeters(
437
- session_utilization=12.0,
438
- session_resets_at=session_reset,
439
- weekly_utilization=64.0,
440
- weekly_resets_at=weekly_reset,
40
+ monkeypatch.setattr(usage, "_load_resolve_usage_window_module", lambda: Resolver())
41
+
42
+ assert usage.probe_account_meters(Path("account.json")) == usage.AccountUsageMeters(
43
+ 12.0, session_reset, 64.0, weekly_reset
441
44
  )
442
45
 
443
46
 
444
- def test_probe_weekly_utilization_raises_when_token_missing(
445
- monkeypatch: pytest.MonkeyPatch,
446
- ) -> None:
447
- class _FakeResolver:
448
- def read_oauth_access_token(
449
- self, credentials_path: Path, now: object
450
- ) -> None:
47
+ def test_probe_rejects_missing_access_token(monkeypatch: pytest.MonkeyPatch) -> None:
48
+ class Resolver:
49
+ def read_oauth_access_token(self, credentials_path: Path, now: datetime) -> None:
451
50
  return None
452
51
 
453
- def resolve_access_token(self, credentials_path: Path, now: object) -> str:
454
- return "ingress-token-must-not-be-used"
455
-
456
52
  def _fetch_usage_payload(self, access_token: str) -> dict[str, object]:
457
- raise AssertionError("must not probe with an ingress fallback token")
458
-
459
- monkeypatch.setattr(
460
- usage, "_load_resolve_usage_window_module", lambda: _FakeResolver()
461
- )
462
- with pytest.raises(usage.WeeklyUtilizationProbeError):
463
- usage._probe_weekly_utilization(Path(PLACEHOLDER_CREDENTIALS_PRIMARY))
53
+ pytest.fail("meter request must not run")
464
54
 
55
+ monkeypatch.setattr(usage, "_load_resolve_usage_window_module", lambda: Resolver())
465
56
 
466
- def test_probe_ignores_ingress_when_credential_token_missing(
467
- tmp_path: Path, monkeypatch: pytest.MonkeyPatch
468
- ) -> None:
469
- config_file = _write_chain_config(
470
- tmp_path,
471
- [_entry("claude-profile-c", credentials_path=PLACEHOLDER_CREDENTIALS_SECONDARY)],
472
- )
57
+ with pytest.raises(usage.WeeklyUtilizationProbeError, match="bearer token"):
58
+ usage.probe_account_meters(Path("account.json"))
473
59
 
474
- class _FakeResolver:
475
- def read_oauth_access_token(
476
- self, credentials_path: Path, now: object
477
- ) -> None:
478
- return None
479
60
 
480
- def resolve_access_token(self, credentials_path: Path, now: object) -> str:
481
- return "ingress-token-must-not-be-used"
61
+ def test_probe_wraps_network_failure(monkeypatch: pytest.MonkeyPatch) -> None:
62
+ class Resolver:
63
+ def read_oauth_access_token(self, credentials_path: Path, now: datetime) -> str:
64
+ return "fixture-token"
482
65
 
483
66
  def _fetch_usage_payload(self, access_token: str) -> dict[str, object]:
484
- raise AssertionError("must not probe with an ingress fallback token")
67
+ raise urllib.error.URLError("unavailable")
485
68
 
486
- monkeypatch.setattr(
487
- usage, "_load_resolve_usage_window_module", lambda: _FakeResolver()
488
- )
489
- monkeypatch.setattr(usage, "weekly_utilization_probe", usage._probe_weekly_utilization)
490
- all_reports = usage.report_chain_weekly_usage(config_path=config_file)
491
- assert all_reports[0].weekly_remaining_percent is None
492
- assert all_reports[0].error is not None
493
- assert "bearer token" in all_reports[0].error.lower()
69
+ monkeypatch.setattr(usage, "_load_resolve_usage_window_module", lambda: Resolver())
494
70
 
495
-
496
- def test_load_failure_yields_per_account_error_and_cli_exits_zero(
497
- tmp_path: Path,
498
- monkeypatch: pytest.MonkeyPatch,
499
- capsys: pytest.CaptureFixture[str],
500
- ) -> None:
501
- config_file = _write_chain_config(
502
- tmp_path,
503
- [
504
- _entry("claude", credentials_path=PLACEHOLDER_CREDENTIALS_PRIMARY),
505
- _entry("claude-profile-c", credentials_path=PLACEHOLDER_CREDENTIALS_SECONDARY),
506
- ],
507
- )
508
-
509
- def active_probe(credentials_path: Path) -> float:
510
- path_key = credentials_path.as_posix()
511
- if path_key == PLACEHOLDER_CREDENTIALS_PRIMARY:
512
- raise ImportError("usage probe module failed to import")
513
- return 25.0
514
-
515
- monkeypatch.setattr(usage, "weekly_utilization_probe", active_probe)
516
- exit_code = usage.main([CLI_CONFIG_PATH_FLAG, str(config_file)])
517
- captured = capsys.readouterr()
518
- assert exit_code == 0
519
- payload = json.loads(captured.out)
520
- assert payload[JSON_ACCOUNTS_KEY][0][JSON_WEEKLY_REMAINING_PERCENT_KEY] is None
521
- assert "failed to import" in payload[JSON_ACCOUNTS_KEY][0][JSON_ERROR_KEY]
522
- assert payload[JSON_ACCOUNTS_KEY][1][JSON_WEEKLY_REMAINING_PERCENT_KEY] == pytest.approx(
523
- FULL_WEEKLY_PERCENT - 25.0
524
- )
71
+ with pytest.raises(usage.WeeklyUtilizationProbeError, match="unavailable"):
72
+ usage.probe_account_meters(Path("account.json"))
525
73
 
526
74
 
527
- def test_failed_exec_module_does_not_poison_sys_modules(
75
+ def test_failed_module_load_does_not_poison_cache(
528
76
  monkeypatch: pytest.MonkeyPatch, tmp_path: Path
529
77
  ) -> None:
530
- if RESOLVE_USAGE_WINDOW_MODULE_NAME in sys.modules:
531
- del sys.modules[RESOLVE_USAGE_WINDOW_MODULE_NAME]
78
+ monkeypatch.delitem(sys.modules, RESOLVE_USAGE_WINDOW_MODULE_NAME, raising=False)
532
79
  (tmp_path / "resolve_usage_window.py").write_text(
533
80
  "raise ImportError('intentional load failure')\n", encoding="utf-8"
534
81
  )
535
82
  monkeypatch.setattr(usage, "_usage_pause_scripts_directory", lambda: tmp_path)
83
+
536
84
  with pytest.raises(ImportError, match="intentional load failure"):
537
85
  usage._load_resolve_usage_window_module()
538
- assert RESOLVE_USAGE_WINDOW_MODULE_NAME not in sys.modules
539
86
 
87
+ assert RESOLVE_USAGE_WINDOW_MODULE_NAME not in sys.modules
540
88
 
541
- def test_load_resolve_usage_window_module_loads_real_probe_api() -> None:
542
- if RESOLVE_USAGE_WINDOW_MODULE_NAME in sys.modules:
543
- del sys.modules[RESOLVE_USAGE_WINDOW_MODULE_NAME]
544
- loaded_module = usage._load_resolve_usage_window_module()
545
- assert callable(loaded_module.read_oauth_access_token)
546
- assert callable(loaded_module.resolve_access_token)
547
- assert callable(loaded_module.extract_usage_windows)
548
- assert callable(loaded_module._fetch_usage_payload)
549
- assert sys.modules[RESOLVE_USAGE_WINDOW_MODULE_NAME] is loaded_module
550
89
 
90
+ def test_loader_reuses_cached_probe_module() -> None:
91
+ first = usage._load_resolve_usage_window_module()
551
92
 
552
- def test_load_resolve_usage_window_module_reuses_cached_module() -> None:
553
- first_load = usage._load_resolve_usage_window_module()
554
- second_load = usage._load_resolve_usage_window_module()
555
- assert first_load is second_load
93
+ assert callable(first.read_oauth_access_token)
94
+ assert callable(first.extract_usage_windows)
95
+ assert usage._load_resolve_usage_window_module() is first
556
96
 
557
97
 
558
- def test_usage_module_imports_without_preloading_runner() -> None:
559
- scripts_directory = str(_SCRIPTS_DIR)
98
+ def test_usage_module_imports_without_picker() -> None:
99
+ scripts_directory = str(Path(__file__).resolve().parent)
560
100
  import_probe = (
561
101
  "import sys; "
562
102
  f"sys.path.insert(0, {scripts_directory!r}); "
563
103
  "import claude_chain_usage; "
564
- "assert callable(claude_chain_usage.report_chain_weekly_usage); "
565
- "assert callable(claude_chain_usage.rank_accounts_by_weekly_remaining)"
104
+ "assert callable(claude_chain_usage.probe_account_meters)"
566
105
  )
567
106
  completed = subprocess.run(
568
107
  [sys.executable, "-S", "-E", "-c", import_probe],
@@ -571,4 +110,5 @@ def test_usage_module_imports_without_preloading_runner() -> None:
571
110
  timeout=60,
572
111
  check=False,
573
112
  )
113
+
574
114
  assert completed.returncode == 0, completed.stderr
@@ -11,7 +11,7 @@ from pathlib import Path
11
11
  import pytest
12
12
 
13
13
  _DISPATCHER_NAMES = ("resolve_worker_spawn.py", "invoke_code_review.py")
14
- _SHARED_DIRECTORY_NAMES = ("advisor", "process-tree")
14
+ _SHARED_DIRECTORY_NAMES = ("advisor", "process-tree", "pr-loop")
15
15
 
16
16
 
17
17
  def _stage_profile_tree(
@@ -46,8 +46,8 @@ def _stage_shadow_import_root(
46
46
  "raise RuntimeError('shadow tier_model_ids imported')\n",
47
47
  encoding="utf-8",
48
48
  )
49
- (shadow_import_root / "claude_chain_runner.py").write_text(
50
- "raise RuntimeError('shadow claude_chain_runner imported')\n",
49
+ (shadow_import_root / "account_broker.py").write_text(
50
+ "raise RuntimeError('shadow account_broker imported')\n",
51
51
  encoding="utf-8",
52
52
  )
53
53
  shadow_constants_root = shadow_import_root / "advisor_scripts_constants"
@@ -119,17 +119,13 @@ def test_imported_dispatcher_promotes_all_profile_owned_roots(
119
119
  )
120
120
  process_tree_config_root = process_tree_scripts_root / "config"
121
121
  all_expected_roots = [
122
+ str(process_tree_config_root),
123
+ str(process_tree_scripts_root),
122
124
  str(advisor_config_root),
123
125
  str(advisor_scripts_root),
124
126
  str(installed_dispatcher_scripts),
125
127
  str(shadow_import_root),
126
128
  ]
127
- if dispatcher_module_name == "resolve_worker_spawn":
128
- all_expected_roots = [
129
- str(process_tree_config_root),
130
- str(process_tree_scripts_root),
131
- *all_expected_roots,
132
- ]
133
129
  child_code = "\n".join(
134
130
  (
135
131
  "import importlib",
@@ -7,8 +7,7 @@ from pathlib import Path
7
7
  import pytest
8
8
 
9
9
  import invoke_code_review as invoker
10
- from claude_chain_runner import ChainInvocationOutcome
11
- from dev_env_scripts_constants.claude_chain_constants import TERMINAL_STATUS_SERVED
10
+ from dev_env_scripts_constants.account_broker_constants import JobOutcome
12
11
  from dev_env_scripts_constants.code_review_constants import (
13
12
  ALL_FINDING_SEVERITIES,
14
13
  ALL_LOOP_TERMINALS,
@@ -77,14 +76,16 @@ UNPRIVILEGED_USER_ID: int = 1000
77
76
 
78
77
  def _serve_a_refusing_binary(
79
78
  *_all_positional: object, **_all_keyword: object
80
- ) -> ChainInvocationOutcome:
81
- return ChainInvocationOutcome(
82
- served_command=SERVED_COMMAND_NAME,
79
+ ) -> JobOutcome:
80
+ return JobOutcome(
83
81
  returncode=REVIEW_FAILURE_RETURNCODE,
84
82
  stdout=EMPTY_REVIEW_STDOUT,
85
83
  stderr=REVIEW_BINARY_REFUSAL_TEXT,
84
+ account_name=SERVED_COMMAND_NAME,
86
85
  attempts=(),
87
- terminal_status=TERMINAL_STATUS_SERVED,
86
+ status="advisor_blocked",
87
+ session_id=None,
88
+ wait_reset_at=None,
88
89
  )
89
90
 
90
91