-
Notifications
You must be signed in to change notification settings - Fork 0
streaming-server prefer local IFC path:接 host_local_path/local_path 走 shared volume #96
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,6 +55,7 @@ def __init__( | |
| config_path: Path | None = None, | ||
| timeout_seconds: int = 600, | ||
| work_dir: Path | None = None, | ||
| storage_root: Path | None = None, | ||
| ) -> None: | ||
| self.repo_root = Path(repo_root) | ||
| self.powershell_exe = powershell_exe | ||
|
|
@@ -64,6 +65,14 @@ def __init__( | |
| self.timeout_seconds = int(timeout_seconds) | ||
| self.work_dir = Path(work_dir) if work_dir else self.repo_root | ||
| self.ps1_path = self.repo_root / "scripts" / "convert-ifc-to-usdc.ps1" | ||
| # streaming-server-prefer-local-ifc-path: shared volume sandbox base for | ||
| # dispatch payload host_local_path / local_path. Defaults to env STORAGE_ROOT | ||
| # (compose 對齊),or cwd 作為 host-native fallback。 | ||
| if storage_root is not None: | ||
| self.storage_root = Path(storage_root).resolve() | ||
| else: | ||
| env_root = os.environ.get("STORAGE_ROOT") | ||
| self.storage_root = (Path(env_root) if env_root else Path.cwd()).resolve() | ||
|
Comment on lines
+70
to
+75
Comment on lines
+74
to
+75
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎.
Comment on lines
+74
to
+75
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||
|
|
||
| # -- preflight ----------------------------------------------------------- | ||
|
|
||
|
|
@@ -162,6 +171,15 @@ def _resolve_local_ifc(self, ifc_ready_event: Mapping[str, Any]) -> Path: | |
| raise ConversionAuthorityError( | ||
| "invalid_ifc_input", "ifc_ready event is missing ifc_artifact." | ||
| ) | ||
| # streaming-server-prefer-local-ifc-path: 優先用 coordinator 寫到 shared volume | ||
| # 的 IFC,避免重複 HTTP fetch。host_local_path 是 host-native streaming-server | ||
| # 直接讀的 host fs path;local_path 在共享 fs 場景下與其同值,作為 fallback。 | ||
| # 兩者都必須落在 storage_root 之內(防 path traversal)。 | ||
| local = self._try_local_path(artifact.get("host_local_path")) | ||
| if local is None: | ||
| local = self._try_local_path(artifact.get("local_path")) | ||
| if local is not None: | ||
| return local | ||
| url = artifact.get("url") or artifact.get("file_url") or artifact.get( | ||
| "signed_upload_reference" | ||
| ) | ||
|
|
@@ -177,6 +195,38 @@ def _resolve_local_ifc(self, ifc_ready_event: Mapping[str, Any]) -> Path: | |
| ) | ||
| return local | ||
|
|
||
| def _try_local_path(self, candidate: Any) -> Path | None: | ||
| """Resolve a dispatch-payload local path inside storage_root sandbox. | ||
|
|
||
| Returns the resolved Path if the file exists and lies inside | ||
| ``self.storage_root``; returns None when the candidate is missing or | ||
| the file does not yet exist (soft fallback). Raises | ||
| ``ConversionAuthorityError("invalid_ifc_input")`` when the resolved | ||
| path escapes ``storage_root`` (security hard-fail). | ||
| """ | ||
| if not candidate: | ||
| return None | ||
| raw = str(candidate).strip() | ||
| if not raw: | ||
| return None | ||
| path = Path(raw) | ||
| base = self.storage_root | ||
| resolved = ( | ||
| path.resolve() | ||
| if path.is_absolute() | ||
| else (base / path).resolve() | ||
| ) | ||
|
Comment on lines
+214
to
+218
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The new local-path branch calls Useful? React with 👍 / 👎. |
||
| try: | ||
| resolved.relative_to(base) | ||
| except ValueError as exc: | ||
| raise ConversionAuthorityError( | ||
| "invalid_ifc_input", | ||
| f"local IFC path is outside storage_root: {raw}", | ||
| ) from exc | ||
| if not resolved.is_file(): | ||
| return None | ||
| return resolved | ||
|
|
||
| def _url_to_local_path(self, url: str) -> Path | None: | ||
| parsed = urlparse(url) | ||
| scheme = (parsed.scheme or "").lower() | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| # Acceptance — streaming-server-prefer-local-ifc-path | ||
|
|
||
| ## L1 — Unit / pytest | ||
|
|
||
| - `cd bim-streaming-server && python -m pytest tests/test_conversion_authority_api.py -q` **PASS**(含新增 5 case) | ||
| - `cd bim-streaming-server && python -m pytest tests -q` **PASS**(整 suite regression) | ||
| - `cd bim-review-coordinator && npm run verify` **PASS**(11 files / 168 tests,不動 coordinator code) | ||
| - `python -m pytest tests -p no:cacheprovider` **PASS**(root contracts / fakes) | ||
|
|
||
| ## L2 — OpenSpec validate | ||
|
|
||
| - `npx openspec validate streaming-server-prefer-local-ifc-path --strict` **valid** | ||
| - `npx openspec validate --specs --strict` **all passed**(本 change archive 之後仍綠) | ||
|
|
||
| ## L3 — GitNexus | ||
|
|
||
| - Pre-impact:`_ifc_artifact` / `_resolve_local_ifc` / `_url_to_local_path` 的 upstream impact **LOW / MEDIUM**(內部 utility,d=1 callers 在同 module);任一 HIGH/CRITICAL 先回報 | ||
| - Post-change `detect_changes`:staged file set = `conversion_authority.py` + `ifc2usdc_powershell_adapter.py` + `tests/test_conversion_authority_api.py` + openspec change folder(無額外飄逸 file) | ||
|
|
||
| ## L4 — 真實 runtime end-to-end | ||
|
|
||
| - `docker rm -f` 既有 coordinator + viewer container | ||
| - `docker compose -p ai-bim-web-plane-host-kit ... up -d --build coordinator viewer`(用最新 main code) | ||
| - streaming-server host-native 重啟讀新 code | ||
| - Python urllib 等效 Postman ① ②(用 collection 預設值或使用者真實 URL): | ||
| - ① POST 預期 `HTTP 202 / dispatched / download_status:downloaded`(PR #94/#95 + 本 change 累積行為) | ||
| - ② Poll 預期 `conversion_status` 從 `queued` **離開 queued**(用 fallback-friendly fixture 至少能 fail with clear error;真實 URL 可達則 ready + viewer_url 出現) | ||
| - `docker exec coordinator ls /workspace/storage/ifc-cache/<jobId>/source.ifc` 確認 IFC bytes 真實落地(真實 URL 場景) | ||
| - streaming-server log 顯示 path resolution 結果走 host_local_path 分支 | ||
|
|
||
| ## L5 — UI(optional / nice-to-have) | ||
|
|
||
| - 若 conversion 真正 ready + viewer_url 出現:在 browser 開 `http://127.0.0.1:8004/ui/open?session=<lwv_id>` → 302 redirect 到 `127.0.0.1:5173/?session=<id>`,viewer 全螢幕 stream | ||
|
|
||
| ## Stop conditions | ||
|
|
||
| 任一 L1-L4 不 pass:stop,回報給人類。 | ||
|
|
||
| 不要為了 acceptance 而 hack(例如改 fixture 來 pass test、跳 sandboxing 來讓 path 通)。 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honor injected env for
STORAGE_ROOTwhen building the adapter.Line 74 reads process env directly, so
adapter_from_env(env=...)cannot controlSTORAGE_ROOT. This can silently pickcwdas sandbox root and misclassify valid shared-volume paths.Proposed fix
def adapter_from_env(repo_root: Path, env: Mapping[str, str] | None = None): @@ return Ifc2UsdcPowershellConverterAdapter( repo_root=Path(repo_root), powershell_exe=src.get("STREAMING_CONVERSION_POWERSHELL_EXE") or _default_powershell_exe(), kit_exe_path=_path("STREAMING_CONVERSION_KIT_EXE"), hoops_main_path=_path("STREAMING_CONVERSION_HOOPS_MAIN"), config_path=_path("STREAMING_CONVERSION_CONFIG_PATH"), timeout_seconds=timeout, work_dir=_path("STREAMING_CONVERSION_WORK_DIR"), + storage_root=_path("STORAGE_ROOT"), )🤖 Prompt for AI Agents