cdx-manager 0.9.14 → 0.9.15

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.
@@ -14,7 +14,7 @@ from .claude_usage import _decode_jwt_claims
14
14
  from .codex_usage import fetch_codex_rate_limits
15
15
  from .config import PROVIDER_ANTIGRAVITY, PROVIDER_CLAUDE, PROVIDER_CODEX, PROVIDERS, get_cdx_home
16
16
  from .errors import CdxError
17
- from .fs_utils import remove_tree
17
+ from .fs_utils import atomic_write, remove_tree
18
18
  from .session_store import create_session_store
19
19
  from .status_source import find_latest_status_artifact
20
20
 
@@ -266,6 +266,7 @@ def _normalize_status_payload(payload=None):
266
266
  "updated_at": _to_local_iso(payload.get("updated_at") or payload.get("captured_at") or now),
267
267
  "raw_status_text": payload.get("raw_status_text"),
268
268
  "source_ref": payload.get("source_ref"),
269
+ "structured": bool(payload.get("structured")),
269
270
  }
270
271
 
271
272
 
@@ -366,6 +367,12 @@ def _merge_status_payload(current, candidate):
366
367
  if merged.get(field) is None and candidate.get(field) is not None:
367
368
  merged[field] = candidate[field]
368
369
 
370
+ # The structured marker qualifies the source_ref; keep them in sync when
371
+ # the merge adopts the candidate's ref, else a structured rollout ref
372
+ # would read as low-confidence scraped text.
373
+ if merged.get("source_ref") is not None and merged["source_ref"] == candidate.get("source_ref"):
374
+ merged["structured"] = bool(candidate.get("structured"))
375
+
369
376
  merged["updated_at"] = candidate.get("updated_at") or current.get("updated_at")
370
377
  return merged
371
378
 
@@ -386,6 +393,10 @@ def _compute_available_pct(status):
386
393
  def _is_low_confidence_status_source(status):
387
394
  if not status:
388
395
  return False
396
+ if status.get("structured"):
397
+ # Exact rate_limits API data: trustworthy even when it comes from a
398
+ # sessions/rollout path, which otherwise flags scraped-text noise.
399
+ return False
389
400
  source_ref = str(status.get("source_ref") or "").replace(os.sep, "/")
390
401
  return "/sessions/" in source_ref and "/rollout" in source_ref
391
402
 
@@ -425,9 +436,7 @@ def _ensure_claude_attribution_disabled(auth_home):
425
436
  settings = {}
426
437
  settings["includeCoAuthoredBy"] = False
427
438
  try:
428
- with open(settings_path, "w", encoding="utf-8") as handle:
429
- json.dump(settings, handle, indent=2, sort_keys=True)
430
- handle.write("\n")
439
+ atomic_write(settings_path, json.dumps(settings, indent=2, sort_keys=True) + "\n", mode=0o644)
431
440
  except OSError:
432
441
  return False
433
442
  return True
@@ -478,23 +487,29 @@ def create_session_service(options=None):
478
487
  )
479
488
 
480
489
  def _session_runtime(name):
481
- state = store["read_session_state"](name)
482
- if not state:
490
+ found = {}
491
+
492
+ def updater(state):
493
+ if not state:
494
+ return None
495
+ runtime = state.get("runtime")
496
+ if _runtime_is_active(runtime):
497
+ found["runtime"] = runtime
498
+ return None
499
+ if isinstance(runtime, dict) and runtime.get("status") == "running":
500
+ return {
501
+ **state,
502
+ "status": "ready",
503
+ "runtime": {
504
+ **runtime,
505
+ "status": "stale",
506
+ "endedAt": _local_now_iso(),
507
+ },
508
+ }
483
509
  return None
484
- runtime = state.get("runtime")
485
- if _runtime_is_active(runtime):
486
- return runtime
487
- if isinstance(runtime, dict) and runtime.get("status") == "running":
488
- store["write_session_state"](name, {
489
- **state,
490
- "status": "ready",
491
- "runtime": {
492
- **runtime,
493
- "status": "stale",
494
- "endedAt": _local_now_iso(),
495
- },
496
- })
497
- return None
510
+
511
+ store["update_session_state"](name, updater)
512
+ return found.get("runtime")
498
513
 
499
514
  def _validate_new_session_name(name):
500
515
  if not name:
@@ -755,11 +770,14 @@ def create_session_service(options=None):
755
770
  raise CdxError(f"Unknown session: {name}")
756
771
  if session.get("enabled", True) is False:
757
772
  raise CdxError(f"Session is disabled: {name}")
758
- state = store["read_session_state"](name)
759
- if not state:
760
- raise CdxError(f"Session state missing for {name}. Reconnect required.")
761
773
  now = _local_now_iso()
762
- store["write_session_state"](name, {**state, "rehydratedAt": now})
774
+
775
+ def state_updater(state):
776
+ if not state:
777
+ raise CdxError(f"Session state missing for {name}. Reconnect required.")
778
+ return {**state, "rehydratedAt": now}
779
+
780
+ store["update_session_state"](name, state_updater)
763
781
  return store["update_session"](name, lambda s: {
764
782
  **s, "updatedAt": now, "lastLaunchedAt": now
765
783
  })
@@ -768,9 +786,6 @@ def create_session_service(options=None):
768
786
  session = store["get_session"](name)
769
787
  if not session:
770
788
  raise CdxError(f"Unknown session: {name}")
771
- state = store["read_session_state"](name)
772
- if not state:
773
- raise CdxError(f"Session state missing for {name}. Reconnect required.")
774
789
  payload = dict(payload or {})
775
790
  now = _local_now_iso()
776
791
  runtime = {
@@ -782,52 +797,70 @@ def create_session_service(options=None):
782
797
  "label": payload.get("label"),
783
798
  "transcriptPath": payload.get("transcript_path") or payload.get("transcriptPath"),
784
799
  }
785
- store["write_session_state"](name, {
786
- **state,
787
- "status": "running",
788
- "runtime": runtime,
789
- })
800
+
801
+ def updater(state):
802
+ if not state:
803
+ raise CdxError(f"Session state missing for {name}. Reconnect required.")
804
+ return {
805
+ **state,
806
+ "status": "running",
807
+ "runtime": runtime,
808
+ }
809
+
810
+ store["update_session_state"](name, updater)
790
811
  return runtime
791
812
 
792
813
  def finish_session_runtime(name, run_id=None, payload=None):
793
814
  session = store["get_session"](name)
794
815
  if not session:
795
816
  raise CdxError(f"Unknown session: {name}")
796
- state = store["read_session_state"](name)
797
- if not state:
798
- return None
799
- runtime = state.get("runtime") or {}
800
- if run_id and runtime.get("runId") != run_id:
801
- return runtime
802
817
  payload = dict(payload or {})
803
- updated_runtime = {
804
- **runtime,
805
- "status": payload.get("status") or "stopped",
806
- "endedAt": payload.get("endedAt") or _local_now_iso(),
807
- }
808
- if "returncode" in payload:
809
- updated_runtime["returncode"] = payload["returncode"]
810
- store["write_session_state"](name, {
811
- **state,
812
- "status": "ready",
813
- "runtime": updated_runtime,
814
- })
815
- return updated_runtime
818
+ outcome = {}
819
+
820
+ def updater(state):
821
+ if not state:
822
+ return None
823
+ runtime = state.get("runtime") or {}
824
+ if run_id and runtime.get("runId") != run_id:
825
+ outcome["runtime"] = runtime
826
+ return None
827
+ updated_runtime = {
828
+ **runtime,
829
+ "status": payload.get("status") or "stopped",
830
+ "endedAt": payload.get("endedAt") or _local_now_iso(),
831
+ }
832
+ if "returncode" in payload:
833
+ updated_runtime["returncode"] = payload["returncode"]
834
+ outcome["runtime"] = updated_runtime
835
+ return {
836
+ **state,
837
+ "status": "ready",
838
+ "runtime": updated_runtime,
839
+ }
840
+
841
+ store["update_session_state"](name, updater)
842
+ return outcome.get("runtime")
816
843
 
817
844
  def ensure_session_state(name):
818
845
  session = store["get_session"](name)
819
846
  if not session:
820
847
  raise CdxError(f"Unknown session: {name}")
821
- state = store["read_session_state"](name)
822
- if state:
823
- return state
824
- repaired = {
825
- "provider": session["provider"],
826
- "status": "ready",
827
- "rehydratedAt": None,
828
- }
829
- store["write_session_state"](name, repaired)
830
- return repaired
848
+ result = {}
849
+
850
+ def updater(state):
851
+ if state:
852
+ result["state"] = state
853
+ return None
854
+ repaired = {
855
+ "provider": session["provider"],
856
+ "status": "ready",
857
+ "rehydratedAt": None,
858
+ }
859
+ result["state"] = repaired
860
+ return repaired
861
+
862
+ store["update_session_state"](name, updater)
863
+ return result["state"]
831
864
 
832
865
  def list_sessions():
833
866
  return store["list_sessions"]()
@@ -853,22 +886,28 @@ def create_session_service(options=None):
853
886
  updates = _normalize_launch_settings(settings)
854
887
  if not updates:
855
888
  raise CdxError("At least one launch setting is required.")
856
- current = _normalize_launch_settings(session.get("launch") or {}, mark_fast_service_tier=False)
857
- launch = {**current, **updates}
858
- explicit_power = "power" in updates or "reasoning_effort" in updates
859
- if explicit_power and "fast" not in updates and launch.get("fastMode") != "service_tier":
860
- launch["fast"] = False
861
- launch.pop("fastMode", None)
862
- if updates.get("fast") is False:
863
- launch.pop("fastMode", None)
864
- if not any(key in launch for key in ("power", "reasoning_effort", "reasoningEffort")):
865
- launch["power"] = DEFAULT_LAUNCH_SETTINGS["power"]
866
889
  now = _local_now_iso()
867
- return store["update_session"](name, lambda s: {
868
- **s,
869
- "launch": launch,
870
- "updatedAt": now,
871
- })
890
+
891
+ def updater(s):
892
+ # Build the new launch dict from the fresh record inside the store
893
+ # lock; a pre-lock snapshot loses a concurrent setting change.
894
+ current = _normalize_launch_settings(s.get("launch") or {}, mark_fast_service_tier=False)
895
+ launch = {**current, **updates}
896
+ explicit_power = "power" in updates or "reasoning_effort" in updates
897
+ if explicit_power and "fast" not in updates and launch.get("fastMode") != "service_tier":
898
+ launch["fast"] = False
899
+ launch.pop("fastMode", None)
900
+ if updates.get("fast") is False:
901
+ launch.pop("fastMode", None)
902
+ if not any(key in launch for key in ("power", "reasoning_effort", "reasoningEffort")):
903
+ launch["power"] = DEFAULT_LAUNCH_SETTINGS["power"]
904
+ return {
905
+ **s,
906
+ "launch": launch,
907
+ "updatedAt": now,
908
+ }
909
+
910
+ return store["update_session"](name, updater)
872
911
 
873
912
  def unset_launch_settings(name, keys):
874
913
  session = store["get_session"](name)
@@ -880,12 +919,12 @@ def create_session_service(options=None):
880
919
  unknown = [key for key in keys if key not in allowed]
881
920
  if unknown:
882
921
  raise CdxError(f"Unsupported launch setting: {', '.join(unknown)}")
883
- current = dict(session.get("launch") or {})
884
- for key in keys:
885
- current.pop(key, None)
886
922
  now = _local_now_iso()
887
923
 
888
924
  def updater(s):
925
+ current = dict(s.get("launch") or {})
926
+ for key in keys:
927
+ current.pop(key, None)
889
928
  updated = {**s, "updatedAt": now}
890
929
  if current:
891
930
  updated["launch"] = current
@@ -972,6 +1011,9 @@ def create_session_service(options=None):
972
1011
  global_root,
973
1012
  session["provider"],
974
1013
  expected_account_email=expected_account_email,
1014
+ # The shared codex home can hold other accounts' rollouts;
1015
+ # structured payloads there cannot be attributed to ours.
1016
+ trust_unattributed_structured=False,
975
1017
  )
976
1018
  if not artifact:
977
1019
  if _is_low_confidence_status_source(current_status):
@@ -988,6 +1030,7 @@ def create_session_service(options=None):
988
1030
  "updated_at": artifact.get("updated_at"),
989
1031
  "raw_status_text": artifact.get("raw_status_text"),
990
1032
  "source_ref": artifact.get("source_ref"),
1033
+ "structured": artifact.get("structured"),
991
1034
  })
992
1035
  if _is_low_confidence_status_source(current_status) and not _is_low_confidence_status_source(resolved):
993
1036
  record_status(session["name"], resolved)
@@ -1269,14 +1312,10 @@ def create_session_service(options=None):
1269
1312
  bundle_bytes = encode_bundle(payload, include_auth=include_auth, passphrase=passphrase)
1270
1313
  if progress_callback:
1271
1314
  progress_callback({"event": "writing_started", "path": file_path, "bundle_size_bytes": len(bundle_bytes)})
1272
- os.makedirs(os.path.dirname(os.path.abspath(file_path)) or ".", exist_ok=True)
1273
- with open(file_path, "wb") as handle:
1274
- handle.write(bundle_bytes)
1275
- if sys.platform != "win32":
1276
- try:
1277
- os.chmod(file_path, 0o600)
1278
- except OSError:
1279
- pass
1315
+ # Atomic + 0o600 from the start: the export path may live in a
1316
+ # world-traversable directory, and a crash must not clobber a
1317
+ # previous good bundle with a truncated one.
1318
+ atomic_write(os.path.abspath(file_path), bundle_bytes, mode=0o600)
1280
1319
  return {
1281
1320
  "path": file_path,
1282
1321
  "include_auth": include_auth,
@@ -1327,8 +1366,12 @@ def create_session_service(options=None):
1327
1366
  _ensure_private_dir(session_root)
1328
1367
  _ensure_private_dir(auth_home)
1329
1368
 
1369
+ existing_state_before = None
1330
1370
  if is_existing and merge:
1331
1371
  existing_record = store["get_session"](name) or {}
1372
+ # replace_session resets the state file to defaults, so the
1373
+ # local state must be captured before it runs.
1374
+ existing_state_before = store["read_session_state"](name) or {}
1332
1375
  bundle_record = {
1333
1376
  **session_payload,
1334
1377
  "provider": provider,
@@ -1352,13 +1395,12 @@ def create_session_service(options=None):
1352
1395
  store["replace_session"](name, session_record)
1353
1396
 
1354
1397
  state = (payload.get("states") or {}).get(name)
1355
- if state is not None:
1356
- if is_existing and merge:
1357
- existing_state = store["read_session_state"](name) or {}
1358
- merged_state = {**state, **{k: v for k, v in existing_state.items() if v is not None}}
1398
+ if is_existing and merge:
1399
+ merged_state = {**(state or {}), **{k: v for k, v in (existing_state_before or {}).items() if v is not None}}
1400
+ if merged_state:
1359
1401
  store["write_session_state"](name, merged_state)
1360
- else:
1361
- store["write_session_state"](name, state)
1402
+ elif state is not None:
1403
+ store["write_session_state"](name, state)
1362
1404
 
1363
1405
  for item in (payload.get("profiles") or {}).get(name, []):
1364
1406
  rel_path = _safe_relpath(item.get("path"))
@@ -1371,13 +1413,8 @@ def create_session_service(options=None):
1371
1413
  if is_existing and merge and os.path.exists(dest_path):
1372
1414
  continue
1373
1415
  _ensure_private_dir(os.path.dirname(dest_path))
1374
- with open(dest_path, "wb") as handle:
1375
- handle.write(content)
1376
- if sys.platform != "win32":
1377
- try:
1378
- os.chmod(dest_path, 0o600)
1379
- except OSError:
1380
- pass
1416
+ # Decrypted credentials: 0o600 from creation, no umask window.
1417
+ atomic_write(dest_path, content, mode=0o600)
1381
1418
 
1382
1419
  return {
1383
1420
  "path": file_path,
@@ -260,6 +260,16 @@ def create_session_store(base_dir):
260
260
  with _file_lock(lock_file):
261
261
  _write_session_state_unlocked(name, state)
262
262
 
263
+ def update_session_state(name, updater):
264
+ # Read-modify-write under one lock; separate read/write calls let a
265
+ # concurrent writer's update be clobbered between the two.
266
+ with _file_lock(lock_file):
267
+ state = _read_session_state_unlocked(name)
268
+ updated = updater(state)
269
+ if updated is not None:
270
+ _write_session_state_unlocked(name, updated)
271
+ return updated
272
+
263
273
  def append_launch_history(entry):
264
274
  with _file_lock(lock_file):
265
275
  _ensure_dir(state_dir)
@@ -303,6 +313,7 @@ def create_session_store(base_dir):
303
313
  "replace_session": replace_session,
304
314
  "read_session_state": read_session_state,
305
315
  "write_session_state": write_session_state,
316
+ "update_session_state": update_session_state,
306
317
  "append_launch_history": append_launch_history,
307
318
  "list_launch_history": list_launch_history,
308
319
  }
@@ -19,7 +19,7 @@ _KEY_VALUE_PATTERNS = [
19
19
  ("usage_pct", re.compile(r"usage_pct\s*[:=]\s*(\d{1,3})%?", re.I)),
20
20
  ("remaining_5h_pct", re.compile(r"remaining_?5h_pct\s*[:=]\s*(\d{1,3})%?", re.I)),
21
21
  ("remaining_week_pct", re.compile(r"remaining_?week_pct\s*[:=]\s*(\d{1,3})%?", re.I)),
22
- ("credits", re.compile(r"credits?\s*[:=]\s*([\d, ]*\d[\d, ]*)\s*(?:credits?)?", re.I)),
22
+ ("credits", re.compile(r"credits?\s*[:=]\s*(\d[\d, ]*(?:\.\d+)?)\s*(?:credits?)?", re.I)),
23
23
  ("remaining_5h_pct", re.compile(r"5h\s+limit\s*:\s*\[[^\]]*\]\s*(\d{1,3})%\s*left", re.I)),
24
24
  ("remaining_week_pct", re.compile(r"weekly\s+limit\s*:\s*\[[^\]]*\]\s*(\d{1,3})%\s*left", re.I)),
25
25
  ("remaining_5h_pct", re.compile(r"5h\s+limit\s*:\s*\[[^\]]*\]\s*(\d{1,3})(?:%|\b)", re.I)),
@@ -94,6 +94,15 @@ def _coerce_percentage(value):
94
94
  return None
95
95
 
96
96
 
97
+ def _is_zero_credit_balance(value):
98
+ if value in (None, ""):
99
+ return True
100
+ try:
101
+ return float(str(value).replace(",", "").strip()) == 0
102
+ except (TypeError, ValueError):
103
+ return False
104
+
105
+
97
106
  def _extract_structured_rate_limits(record):
98
107
  if not isinstance(record, dict):
99
108
  return None
@@ -123,7 +132,11 @@ def _extract_structured_rate_limits(record):
123
132
  }
124
133
 
125
134
  balance = credits.get("balance")
126
- if balance not in (None, ""):
135
+ if balance not in (None, "") and not (
136
+ _is_zero_credit_balance(balance)
137
+ and not credits.get("hasCredits")
138
+ and not credits.get("unlimited")
139
+ ):
127
140
  result["credits"] = str(balance).strip()
128
141
 
129
142
  result["reset_at"] = result["reset_week_at"] or result["reset_5h_at"]
@@ -277,6 +290,8 @@ def _extract_jsonl_texts(file_path, provider=None):
277
290
  "timestamp": record.get("timestamp"),
278
291
  "text": structured["raw_status_text"],
279
292
  "structured": structured,
293
+ # Exact API data, as trustworthy as a /status screen.
294
+ "trusted": True,
280
295
  })
281
296
  payload_texts = _collect_text_values(record.get("payload") or {})
282
297
  for candidate in payload_texts:
@@ -296,7 +311,11 @@ def _extract_log_block(file_path, provider=None):
296
311
  text = _safe_read_text(file_path)
297
312
  if not text:
298
313
  return []
299
- return _extract_status_blocks_from_text(text, provider=provider, source_ref=file_path, timestamp=None)
314
+ items = _extract_status_blocks_from_text(text, provider=provider, source_ref=file_path, timestamp=None)
315
+ for item in items:
316
+ # A /status screen captured in a terminal log is authoritative.
317
+ item["trusted"] = True
318
+ return items
300
319
 
301
320
 
302
321
  def _parse_month_index(name):
@@ -422,7 +441,14 @@ def extract_named_statuses_from_text(text):
422
441
  for field, pattern in _KEY_VALUE_PATTERNS:
423
442
  if field not in result:
424
443
  m = pattern.search(normalized)
425
- if m:
444
+ if not m:
445
+ continue
446
+ if field == "credits":
447
+ # Keep the decimal part; a zero balance is no credit at all.
448
+ value = m[1].replace(",", "").replace(" ", "")
449
+ if not _is_zero_credit_balance(value):
450
+ result[field] = value
451
+ else:
426
452
  result[field] = int(re.sub(r"\D", "", m[1]))
427
453
 
428
454
  # Claude "Current session / Current week" block
@@ -596,12 +622,49 @@ def _sort_recent(paths):
596
622
  }
597
623
  return sorted(
598
624
  candidate_stats,
599
- key=lambda fp: candidate_stats[fp].st_mtime,
625
+ key=lambda fp: (candidate_stats[fp].st_mtime, fp),
600
626
  reverse=True,
601
627
  )
602
628
 
603
629
 
604
- def find_latest_status_artifact(root_dir, provider=None, expected_account_email=None):
630
+ _STATUS_VALUE_FIELDS = (
631
+ "usage_pct",
632
+ "remaining_5h_pct",
633
+ "remaining_week_pct",
634
+ "credits",
635
+ "reset_5h_at",
636
+ "reset_week_at",
637
+ "reset_at",
638
+ )
639
+
640
+
641
+ def _backfill_missing_fields(target, source):
642
+ for field in _STATUS_VALUE_FIELDS:
643
+ if target.get(field) is None and source.get(field) is not None:
644
+ target[field] = source[field]
645
+
646
+
647
+ def _record_timestamp_epoch(value):
648
+ """Best-effort epoch seconds from a record timestamp (ISO string, epoch s or ms)."""
649
+ if value in (None, ""):
650
+ return None
651
+ try:
652
+ number = float(value)
653
+ except (TypeError, ValueError):
654
+ try:
655
+ return datetime.fromisoformat(str(value).replace("Z", "+00:00")).timestamp()
656
+ except (TypeError, ValueError):
657
+ return None
658
+ # ponytail: >1e11 means epoch milliseconds (that's year 5138 in seconds)
659
+ return number / 1000 if number > 1e11 else number
660
+
661
+
662
+ def find_latest_status_artifact(
663
+ root_dir,
664
+ provider=None,
665
+ expected_account_email=None,
666
+ trust_unattributed_structured=True,
667
+ ):
605
668
  priority_candidates, history_candidates = _collect_candidate_files(root_dir)
606
669
  candidates = (
607
670
  _sort_recent(priority_candidates)
@@ -634,35 +697,48 @@ def find_latest_status_artifact(root_dir, provider=None, expected_account_email=
634
697
  }
635
698
  if not parsed:
636
699
  continue
637
- ts = candidate.get("timestamp")
638
- try:
639
- score = float(ts) if ts else 0
640
- except (TypeError, ValueError):
641
- score = 0
700
+ # Score on the record's own time so sources stay comparable: falling
701
+ # back to file mtime would let a stale block in an actively-appended
702
+ # jsonl outrank a genuinely fresher terminal log.
703
+ epoch = _record_timestamp_epoch(candidate.get("timestamp"))
642
704
  src_file = re.sub(r":\d+$", "", candidate["source_ref"])
643
705
  stat = _safe_stat(src_file)
644
- if not score and stat:
706
+ if epoch is not None:
707
+ score = epoch
708
+ elif stat:
645
709
  score = stat.st_mtime
646
- priority = 2 if src_file.endswith(".log") else 1
710
+ else:
711
+ score = 0
712
+ # Each record producer stamps "trusted" where it knows the source kind.
713
+ # Untrusted records are text scraped from jsonl payloads: they can be
714
+ # conversational noise quoting an old status block.
715
+ trusted = bool(candidate.get("trusted"))
716
+ # Structured payloads carry no account identity, so the email guard
717
+ # above cannot filter them. In roots shared by several accounts the
718
+ # caller demotes them below account-verified /status screens.
719
+ if trusted and candidate.get("structured") and not trust_unattributed_structured:
720
+ trusted = False
721
+ priority = 2 if trusted else 1
647
722
 
648
723
  if best is None or (priority, score) >= (best["priority"], best["score"]):
724
+ previous = best
649
725
  best = {
650
726
  "priority": priority,
651
727
  "score": score,
652
728
  "source_ref": candidate["source_ref"],
729
+ "structured": bool(candidate.get("structured")),
653
730
  **parsed,
654
731
  }
655
- if ts:
656
- try:
657
- best["updated_at"] = datetime.fromtimestamp(
658
- float(ts) / 1000, tz=timezone.utc
659
- ).astimezone().isoformat()
660
- except (TypeError, ValueError):
661
- pass
662
- if "updated_at" not in best:
663
- if stat:
664
- best["updated_at"] = datetime.fromtimestamp(
665
- stat.st_mtime, tz=timezone.utc
666
- ).astimezone().isoformat()
732
+ if score:
733
+ best["updated_at"] = datetime.fromtimestamp(
734
+ score, tz=timezone.utc
735
+ ).astimezone().isoformat()
736
+ # A fresher-but-partial winner (e.g. rate_limits with only the
737
+ # primary window) must not erase fields an equally trusted
738
+ # candidate provided.
739
+ if previous and previous["priority"] == priority:
740
+ _backfill_missing_fields(best, previous)
741
+ elif priority == best["priority"]:
742
+ _backfill_missing_fields(best, parsed)
667
743
 
668
744
  return best
@@ -1,4 +1,4 @@
1
- from datetime import datetime
1
+ from datetime import datetime, timedelta
2
2
  from decimal import ROUND_HALF_UP, Decimal, InvalidOperation
3
3
 
4
4
  from .cli_render import (
@@ -328,12 +328,25 @@ def _parse_reset_timestamp(value):
328
328
  return parsed.timestamp()
329
329
  except (TypeError, ValueError):
330
330
  pass
331
- try:
332
- parsed = datetime.strptime(text, "%b %d %H:%M")
333
- except (TypeError, ValueError):
334
- return None
335
331
  now = datetime.now().astimezone()
336
- parsed = parsed.replace(year=now.year, tzinfo=now.tzinfo)
332
+ parsed = None
333
+ # Parse with an explicit year: strptime's implicit 1900 rejects "Feb 29",
334
+ # and the current year may not hold the date at all around new year.
335
+ for year in (now.year, now.year + 1):
336
+ try:
337
+ parsed = datetime.strptime(f"{year} {text}", "%Y %b %d %H:%M").replace(tzinfo=now.tzinfo)
338
+ break
339
+ except (TypeError, ValueError):
340
+ continue
341
+ if parsed is None:
342
+ return None
343
+ # A year-less date nearly a year in the past is a year wrap ("Jan 2"
344
+ # rendered on Dec 31), not a reset that passed months ago.
345
+ if parsed < now - timedelta(days=182):
346
+ try:
347
+ parsed = parsed.replace(year=parsed.year + 1)
348
+ except ValueError:
349
+ pass
337
350
  return parsed.timestamp()
338
351
 
339
352
 
@@ -236,6 +236,10 @@ def check_logics_manager_for_update(base_dir, env=None, now_fn=None, runner=None
236
236
  return None
237
237
 
238
238
  latest_version = fetch_latest_logics_manager_version(env=env)
239
+ if not latest_version:
240
+ # Don't cache a failed fetch: a None latest_version would hide a
241
+ # real update for the whole TTL after the registry recovers.
242
+ return None
239
243
  payload = {
240
244
  "checked_at": now_ts,
241
245
  "latest_version": latest_version,
@@ -204,7 +204,9 @@ def run_update_plan(plan, runner=None, env=None):
204
204
  "stdout": _result_text(result, "stdout"),
205
205
  "stderr": _result_text(result, "stderr"),
206
206
  })
207
- if code not in (0, None):
207
+ # A runner that reports no return code is a failure, not a success:
208
+ # treating None as ok would let a failed step continue the plan.
209
+ if code != 0:
208
210
  break
209
211
  return results
210
212