tensor-grep 1.76.2__tar.gz → 1.76.3__tar.gz

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 (135) hide show
  1. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/PKG-INFO +1 -1
  2. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/pyproject.toml +2 -1
  3. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/Cargo.lock +1 -1
  4. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/Cargo.toml +2 -1
  5. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/session_daemon.py +87 -4
  6. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/LICENSE +0 -0
  7. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/NOTICE +0 -0
  8. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/README.md +0 -0
  9. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/deny.toml +0 -0
  10. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/rust-toolchain.toml +0 -0
  11. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/src/backend_ast.rs +0 -0
  12. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/src/backend_ast_workflow.rs +0 -0
  13. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/src/backend_cpu.rs +0 -0
  14. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/src/bin/tg_search_fast.rs +0 -0
  15. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/src/cli.rs +0 -0
  16. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/src/crossover.rs +0 -0
  17. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/src/editor_plane.rs +0 -0
  18. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/src/gpu_native.rs +0 -0
  19. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/src/index.rs +0 -0
  20. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/src/index_lock.rs +0 -0
  21. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/src/lib.rs +0 -0
  22. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/src/main.rs +0 -0
  23. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/src/mmap_arrow.rs +0 -0
  24. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/src/native_search.rs +0 -0
  25. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/src/python_sidecar.rs +0 -0
  26. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/src/rg_passthrough.rs +0 -0
  27. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/src/routing.rs +0 -0
  28. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/src/runtime_paths.rs +0 -0
  29. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_ast_backend.rs +0 -0
  30. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_ast_cache.rs +0 -0
  31. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_ast_grep_embed.rs +0 -0
  32. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_ast_rewrite.rs +0 -0
  33. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_audit_manifest_symlink_unix.rs +0 -0
  34. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_gpu_native.rs +0 -0
  35. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_gpu_native_error_handling.rs +0 -0
  36. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_gpu_native_graphs.rs +0 -0
  37. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_gpu_native_integration.rs +0 -0
  38. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_gpu_native_long_lines.rs +0 -0
  39. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_gpu_native_streams.rs +0 -0
  40. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_index.rs +0 -0
  41. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_index_lock_concurrency.rs +0 -0
  42. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_mmap_arrow.rs +0 -0
  43. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_native_search.rs +0 -0
  44. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_output.rs +0 -0
  45. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_public_native_cli_parity.rs +0 -0
  46. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_python_bootstrap.rs +0 -0
  47. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_release_profile.rs +0 -0
  48. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_replace.rs +0 -0
  49. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_resident_worker.rs +0 -0
  50. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_routing.rs +0 -0
  51. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_runtime_path_resolution.rs +0 -0
  52. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_schema_compat.rs +0 -0
  53. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_search.rs +0 -0
  54. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_search_golden.rs +0 -0
  55. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_sidecar_ipc.rs +0 -0
  56. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/rust_core/tests/test_smart_routing.rs +0 -0
  57. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/__init__.py +0 -0
  58. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/__main__.py +0 -0
  59. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/backends/__init__.py +0 -0
  60. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/backends/ast_backend.py +0 -0
  61. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/backends/ast_wrapper_backend.py +0 -0
  62. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/backends/base.py +0 -0
  63. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/backends/cpu_backend.py +0 -0
  64. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/backends/cudf_backend.py +0 -0
  65. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/backends/cybert_backend.py +0 -0
  66. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/backends/ripgrep_backend.py +0 -0
  67. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/backends/rust_backend.py +0 -0
  68. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/backends/stringzilla_backend.py +0 -0
  69. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/backends/torch_backend.py +0 -0
  70. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/__init__.py +0 -0
  71. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/_index_lock.py +0 -0
  72. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/agent_capsule.py +0 -0
  73. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/apply_policy.py +0 -0
  74. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/ast_workflows.py +0 -0
  75. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/audit_manifest.py +0 -0
  76. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/bootstrap.py +0 -0
  77. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/checkpoint_store.py +0 -0
  78. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/codemap.py +0 -0
  79. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/commands.py +0 -0
  80. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/docs_coverage.py +0 -0
  81. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/dogfood.py +0 -0
  82. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/evidence_receipt.py +0 -0
  83. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/evidence_signing.py +0 -0
  84. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/formatters/__init__.py +0 -0
  85. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/formatters/base.py +0 -0
  86. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/formatters/csv_fmt.py +0 -0
  87. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/formatters/json_fmt.py +0 -0
  88. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/formatters/ripgrep_fmt.py +0 -0
  89. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/formatters/table_fmt.py +0 -0
  90. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/inventory.py +0 -0
  91. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/lang_go.py +0 -0
  92. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/lang_registry.py +0 -0
  93. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/lsp_external_provider.py +0 -0
  94. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/lsp_provider_setup.py +0 -0
  95. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/lsp_server.py +0 -0
  96. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/main.py +0 -0
  97. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/mcp_server.py +0 -0
  98. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/orient_capsule.py +0 -0
  99. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/progress.py +0 -0
  100. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/repo_map.py +0 -0
  101. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/rg_contract.py +0 -0
  102. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/rule_packs.py +0 -0
  103. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/runtime_paths.py +0 -0
  104. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/scan_guardrails.py +0 -0
  105. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/session_store.py +0 -0
  106. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/cli/subprocess_policy.py +0 -0
  107. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/core/__init__.py +0 -0
  108. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/core/config.py +0 -0
  109. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/core/hardware/__init__.py +0 -0
  110. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/core/hardware/device_detect.py +0 -0
  111. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/core/hardware/device_inventory.py +0 -0
  112. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/core/hardware/memory_manager.py +0 -0
  113. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/core/observability.py +0 -0
  114. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/core/pipeline.py +0 -0
  115. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/core/query_analyzer.py +0 -0
  116. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/core/registration_check.py +0 -0
  117. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/core/reranker.py +0 -0
  118. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/core/result.py +0 -0
  119. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/core/retrieval_bm25.py +0 -0
  120. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/core/retrieval_chunker.py +0 -0
  121. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/core/retrieval_dense.py +0 -0
  122. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/core/retrieval_fusion.py +0 -0
  123. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/core/retrieval_late.py +0 -0
  124. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/core/retrieval_lexical.py +0 -0
  125. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/core/retrieval_scoring.py +0 -0
  126. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/core/semantic_index.py +0 -0
  127. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/io/__init__.py +0 -0
  128. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/io/base.py +0 -0
  129. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/io/directory_scanner.py +0 -0
  130. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/io/reader_cudf.py +0 -0
  131. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/io/reader_dstorage.py +0 -0
  132. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/io/reader_fallback.py +0 -0
  133. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/io/reader_kvikio.py +0 -0
  134. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/perf_guard.py +0 -0
  135. {tensor_grep-1.76.2 → tensor_grep-1.76.3}/src/tensor_grep/sidecar.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tensor-grep
3
- Version: 1.76.2
3
+ Version: 1.76.3
4
4
  Requires-Dist: typer>=0.12,<0.26
5
5
  Requires-Dist: rich>=13.0
6
6
  Requires-Dist: mcp>=1.2.0
@@ -531,11 +531,12 @@ version_variables = [
531
531
 
532
532
 
533
533
 
534
+
534
535
 
535
536
 
536
537
  [project]
537
538
  name = "tensor-grep"
538
- version = "1.76.2"
539
+ version = "1.76.3"
539
540
  description = "High-performance grep-compatible CLI with Rust, GPU, and AST/NLP routing"
540
541
  readme = { file = "rust_core/README.md", content-type = "text/markdown" }
541
542
  requires-python = ">=3.11"
@@ -1579,7 +1579,7 @@ dependencies = [
1579
1579
 
1580
1580
  [[package]]
1581
1581
  name = "tensor_grep_rs"
1582
- version = "1.76.2"
1582
+ version = "1.76.3"
1583
1583
  dependencies = [
1584
1584
  "aho-corasick",
1585
1585
  "anyhow",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "tensor_grep_rs"
3
- version = "1.76.2"
3
+ version = "1.76.3"
4
4
  edition = "2021"
5
5
  description = "Rust core engine for tensor-grep"
6
6
  license = "Apache-2.0"
@@ -439,6 +439,7 @@ path = "src/bin/tg_search_fast.rs"
439
439
 
440
440
 
441
441
 
442
+
442
443
 
443
444
 
444
445
  [dev-dependencies]
@@ -372,14 +372,71 @@ def _release_daemon_start_lock(root: Path) -> None:
372
372
  pass
373
373
 
374
374
 
375
- def _remove_daemon_metadata(root: Path) -> None:
375
+ def _remove_daemon_metadata(
376
+ root: Path, *, expected_pid: int | None = None, expected_port: int | None = None
377
+ ) -> None:
378
+ """Remove ``daemon.json`` for ``root``, guarded against deleting a REPLACEMENT daemon's metadata.
379
+
380
+ Task #143a-a (stale-daemon-metadata race): when ``_probe_daemon`` rejects a daemon over a
381
+ ``package_version`` mismatch (see the skew check in ``_probe_daemon`` below), the client spawns
382
+ a replacement daemon (call it B) while the old daemon (A) keeps running until its own
383
+ idle/max-uptime self-shutdown fires. If A's shutdown path (or ``stop_session_daemon``'s
384
+ PID-kill fallback) unlinked ``daemon.json`` unconditionally, it would delete WHATEVER metadata
385
+ currently exists -- including B's, the healthy replacement -- orphaning B (it keeps serving,
386
+ but no client can discover it) and silently piling up orphaned daemon processes over time.
387
+
388
+ Passing ``expected_pid``/``expected_port`` re-reads the CURRENT on-disk metadata immediately
389
+ before unlinking and only proceeds if its ``pid``/``port`` fields still match -- i.e. the file
390
+ still identifies the exact instance the caller intends to remove. A mismatch (a replacement has
391
+ since published its own metadata), a missing file, or an unparseable/corrupt file are all
392
+ treated as "not mine" and left untouched -- the fail-safe choice, since we cannot prove the file
393
+ is safe to delete, and a corrupt file self-heals the next time a daemon is (re)started via
394
+ ``_spawn_daemon_subprocess``'s unconditional clear below.
395
+
396
+ When BOTH ``expected_pid`` and ``expected_port`` are omitted (the default), removal is
397
+ unconditional -- this mode exists for exactly one legitimate caller, ``_spawn_daemon_subprocess``,
398
+ which holds the daemon-start lock (excluding any concurrent spawn for this root) and is clearing
399
+ the slate for a brand-new instance it is about to launch, so there is no prior "self" identity to
400
+ compare against yet. Every other caller must supply at least one ``expected_*`` value, or skip
401
+ the call entirely when it cannot establish one (see ``stop_session_daemon``).
402
+ """
376
403
  metadata_path = _daemon_metadata_path(root)
404
+ if expected_pid is not None or expected_port is not None:
405
+ current = _read_daemon_metadata(root)
406
+ if current is None:
407
+ return
408
+ if expected_pid is not None and current.get("pid") != expected_pid:
409
+ return
410
+ if expected_port is not None and current.get("port") != expected_port:
411
+ return
377
412
  try:
378
413
  metadata_path.unlink()
379
414
  except OSError:
380
415
  pass
381
416
 
382
417
 
418
+ def _daemon_identity(metadata: dict[str, Any] | None) -> tuple[int | None, int | None]:
419
+ """Best-effort ``(pid, port)`` extraction from daemon metadata (task #143a-a).
420
+
421
+ Used by callers of the guarded ``_remove_daemon_metadata`` above to compute the specific
422
+ instance they intend to remove. Missing or non-numeric fields resolve to ``None`` rather than
423
+ raising, so a malformed or partial metadata dict never turns into a removal call the guard
424
+ cannot safely reason about -- the caller is expected to skip the removal when either half of
425
+ the pair it needs comes back ``None`` (see ``stop_session_daemon``).
426
+ """
427
+ if not metadata:
428
+ return None, None
429
+ try:
430
+ pid: int | None = int(metadata["pid"])
431
+ except (KeyError, TypeError, ValueError):
432
+ pid = None
433
+ try:
434
+ port: int | None = int(metadata["port"])
435
+ except (KeyError, TypeError, ValueError):
436
+ port = None
437
+ return pid, port
438
+
439
+
383
440
  def _pid_looks_like_tg_daemon(pid: int) -> bool:
384
441
  """Best-effort check that ``pid`` is a tensor-grep session daemon (audit I7).
385
442
 
@@ -630,6 +687,12 @@ def _spawn_daemon_subprocess(root: Path) -> None:
630
687
  ``daemon.json`` to appear and does not touch the start-lock -- the caller is responsible for
631
688
  both (holding the lock across this call, and deciding whether/how long to wait afterward).
632
689
  """
690
+ # Task #143a-a: unconditional (force) removal is intentional and safe here -- every caller
691
+ # holds the daemon-start lock for `root` (excluding any concurrent spawn), so nothing else can
692
+ # be publishing fresh metadata for this root while we hold it, and we are about to Popen a
693
+ # brand-new instance that will publish its OWN metadata momentarily. There is no prior "self"
694
+ # identity to compare against yet, unlike the guarded callers in stop_session_daemon and
695
+ # run_session_daemon_server's shutdown path below.
633
696
  _remove_daemon_metadata(root)
634
697
  creationflags = 0
635
698
  env = os.environ.copy()
@@ -772,7 +835,16 @@ def stop_session_daemon(path: str = ".") -> dict[str, Any]:
772
835
  # socket is wedged). Fall back to terminating the recorded pid if it validates.
773
836
  stale_metadata = _read_daemon_metadata(root)
774
837
  killed = _terminate_daemon_by_pid(stale_metadata)
775
- _remove_daemon_metadata(root)
838
+ # Task #143a-a: only remove the metadata that still identifies the STALE daemon we just
839
+ # targeted -- never whatever happens to be on disk by the time we get here. A concurrent
840
+ # autostart elsewhere may have already spawned and published a healthy replacement's
841
+ # daemon.json in the window between the failed probe above and this cleanup; an
842
+ # unconditional unlink would delete that replacement's metadata too. When the stale
843
+ # metadata itself cannot be identified (missing/corrupt), there is nothing safe to
844
+ # remove -- skip rather than guess.
845
+ stale_pid, stale_port = _daemon_identity(stale_metadata)
846
+ if stale_pid is not None:
847
+ _remove_daemon_metadata(root, expected_pid=stale_pid, expected_port=stale_port)
776
848
  return {
777
849
  "version": _SESSION_VERSION,
778
850
  "root": str(root),
@@ -802,7 +874,12 @@ def stop_session_daemon(path: str = ".") -> dict[str, Any]:
802
874
  # validated pid terminate so a wedged daemon is not left running.
803
875
  if _terminate_daemon_by_pid(metadata):
804
876
  stop_method = "pid"
805
- _remove_daemon_metadata(root)
877
+ # Task #143a-a: only remove daemon.json if it still identifies the SAME daemon this call
878
+ # targeted (captured in `metadata` above) -- a replacement may have spawned and published its
879
+ # own metadata in the window since. See _remove_daemon_metadata's docstring for the full race.
880
+ target_pid, target_port = _daemon_identity(metadata)
881
+ if target_pid is not None:
882
+ _remove_daemon_metadata(root, expected_pid=target_pid, expected_port=target_port)
806
883
  response["running"] = False
807
884
  response["root"] = str(root)
808
885
  response["stopped"] = True
@@ -1965,7 +2042,13 @@ def run_session_daemon_server(path: str = ".") -> None:
1965
2042
  finally:
1966
2043
  stop_event.set()
1967
2044
  _flush_demand_metrics_if_dirty(server, force=True)
1968
- _remove_daemon_metadata(root)
2045
+ # Task #143a-a: this daemon must only ever remove ITS OWN daemon.json on shutdown
2046
+ # (idle timeout, max-uptime, or a cooperative "stop" command). If a client already
2047
+ # rejected this daemon over a package_version mismatch and spawned a healthy
2048
+ # replacement, that replacement's metadata is what is on disk by the time THIS daemon
2049
+ # finally shuts itself down -- an unconditional unlink here would delete it out from
2050
+ # under the replacement, orphaning it with no discoverable daemon.json.
2051
+ _remove_daemon_metadata(root, expected_pid=os.getpid(), expected_port=int(port))
1969
2052
 
1970
2053
 
1971
2054
  def _parse_args(argv: list[str]) -> argparse.Namespace:
File without changes
File without changes