mirror of
https://github.com/HKUDS/OpenSpace.git
synced 2026-09-11 22:51:05 +00:00
fix: use hash-based workflow ID to prevent separator collisions
The previous __-joined scheme was not injective: a directory named a__b and a nested path a/b both mapped to the same ID. Use a sha256 hash suffix of the resolved path instead, which is collision-free and keeps the dir name as a human-readable prefix. Added regression test for separator collision case.
This commit is contained in:
parent
0f2dcb896b
commit
028c5b01f9
2 changed files with 31 additions and 16 deletions
|
|
@ -420,15 +420,15 @@ def _build_lineage_payload(skill_id: str, store: SkillStore) -> Dict[str, Any]:
|
|||
|
||||
|
||||
def _workflow_id(workflow_dir: Path) -> str:
|
||||
"""Stable short ID for a workflow directory, unique across roots."""
|
||||
resolved = workflow_dir.resolve()
|
||||
for root in WORKFLOW_ROOTS:
|
||||
try:
|
||||
rel = resolved.relative_to(root.resolve())
|
||||
return f"{root.name}__{'__'.join(rel.parts)}"
|
||||
except ValueError:
|
||||
continue
|
||||
return workflow_dir.name
|
||||
"""Stable short ID for a workflow directory, unique across roots.
|
||||
|
||||
Uses a hash suffix derived from the resolved path to avoid collisions
|
||||
when directory names contain the separator character.
|
||||
"""
|
||||
import hashlib
|
||||
resolved = str(workflow_dir.resolve())
|
||||
path_hash = hashlib.sha256(resolved.encode()).hexdigest()[:8]
|
||||
return f"{workflow_dir.name}_{path_hash}"
|
||||
|
||||
|
||||
def _discover_workflow_dirs() -> List[Path]:
|
||||
|
|
|
|||
|
|
@ -28,27 +28,42 @@ class TestWorkflowId:
|
|||
id_b = dashboard_server._workflow_id(wf_b)
|
||||
|
||||
assert id_a != id_b
|
||||
assert "task1" in id_a
|
||||
assert "task1" in id_b
|
||||
assert id_a.startswith("task1_")
|
||||
assert id_b.startswith("task1_")
|
||||
|
||||
def test_nested_workflow_id_includes_path(self, tmp_path):
|
||||
def test_id_contains_dir_name_and_hash(self, tmp_path):
|
||||
root = tmp_path / "root"
|
||||
wf = root / "sub" / "deep" / "task1"
|
||||
wf = root / "my-task"
|
||||
_make_workflow(wf)
|
||||
|
||||
with patch.object(dashboard_server, "WORKFLOW_ROOTS", [root]):
|
||||
wf_id = dashboard_server._workflow_id(wf)
|
||||
|
||||
assert wf_id == "root__sub__deep__task1"
|
||||
assert wf_id.startswith("my-task_")
|
||||
assert len(wf_id) == len("my-task_") + 8 # 8-char hex hash
|
||||
|
||||
def test_workflow_outside_roots_falls_back_to_name(self, tmp_path):
|
||||
def test_separator_collision_produces_different_ids(self, tmp_path):
|
||||
"""Regression: a dir named 'a__b' must not collide with path a/b."""
|
||||
root = tmp_path / "root"
|
||||
flat = root / "a__b"
|
||||
nested = root / "a" / "b"
|
||||
_make_workflow(flat)
|
||||
_make_workflow(nested)
|
||||
|
||||
with patch.object(dashboard_server, "WORKFLOW_ROOTS", [root]):
|
||||
id_flat = dashboard_server._workflow_id(flat)
|
||||
id_nested = dashboard_server._workflow_id(nested)
|
||||
|
||||
assert id_flat != id_nested
|
||||
|
||||
def test_workflow_outside_roots_still_works(self, tmp_path):
|
||||
wf = tmp_path / "orphan_workflow"
|
||||
_make_workflow(wf)
|
||||
|
||||
with patch.object(dashboard_server, "WORKFLOW_ROOTS", []):
|
||||
wf_id = dashboard_server._workflow_id(wf)
|
||||
|
||||
assert wf_id == "orphan_workflow"
|
||||
assert wf_id.startswith("orphan_workflow_")
|
||||
|
||||
|
||||
class TestDiscoverWorkflowDirs:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue