diff --git a/src/k1link/simulation/gaussian_pipeline_gateway.py b/src/k1link/simulation/gaussian_pipeline_gateway.py index 0ff878b..96e5aa1 100644 --- a/src/k1link/simulation/gaussian_pipeline_gateway.py +++ b/src/k1link/simulation/gaussian_pipeline_gateway.py @@ -18,7 +18,9 @@ import httpx TUS_VERSION: Final = "1.0.0" BUILD_REQUEST_SCHEMA: Final = "gaussian-pipeline.build-request/v1" JOB_SCHEMA: Final = "gaussian-pipeline.job/v1" -RESULT_SCHEMA: Final = "gaussian-pipeline.build-result/v1" +LEGACY_RESULT_SCHEMA: Final = "gaussian-pipeline.build-result/v1" +RESULT_SCHEMA: Final = "gaussian-pipeline.build-result/v2" +RESULT_SCHEMAS: Final = frozenset({LEGACY_RESULT_SCHEMA, RESULT_SCHEMA}) CAPABILITIES_SCHEMA: Final = "gaussian-pipeline.capabilities/v1" ARCHIVE_INGEST_REQUEST_SCHEMA: Final = "gaussian-pipeline.archive-ingest-request/v1" ARCHIVE_INGEST_SCHEMA: Final = "gaussian-pipeline.archive-ingest/v1" @@ -356,7 +358,7 @@ class GaussianPipelineGateway: def get_result(self, job_id: str) -> dict[str, Any]: _safe_id(job_id, "job id") result = self._json("GET", f"/v1/jobs/{quote(job_id, safe='')}/result") - if result.get("schema_version") != RESULT_SCHEMA or result.get("job_id") != job_id: + if result.get("schema_version") not in RESULT_SCHEMAS or result.get("job_id") != job_id: raise GaussianPipelineGatewayError("Gaussian result identity does not match") _validate_runtime_provenance(result) return result diff --git a/src/k1link/simulation/projects.py b/src/k1link/simulation/projects.py index f25c07b..79e81af 100644 --- a/src/k1link/simulation/projects.py +++ b/src/k1link/simulation/projects.py @@ -679,8 +679,9 @@ def _world_manifest(project_id: str, artifacts: list[dict[str, Any]]) -> dict[st "streamed_sog_url": url_for("stream-manifest"), }, "collision": { - "mesh_url": url_for("collision-mesh"), - "available": url_for("collision-mesh") is not None, + "mesh_url": url_for("rover-terrain-mesh") or url_for("collision-mesh"), + "available": (url_for("rover-terrain-mesh") or url_for("collision-mesh")) + is not None, }, "transforms": { "world_from_visual": [1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1], diff --git a/tests/test_gaussian_pipeline_gateway.py b/tests/test_gaussian_pipeline_gateway.py index 45b3ee1..6d073b0 100644 --- a/tests/test_gaussian_pipeline_gateway.py +++ b/tests/test_gaussian_pipeline_gateway.py @@ -350,7 +350,14 @@ def test_gateway_rejects_symlink_source_and_token(tmp_path: Path) -> None: ) -def test_gateway_submits_tracks_and_imports_digest_bound_artifact(tmp_path: Path) -> None: +@pytest.mark.parametrize( + "result_schema", + ["gaussian-pipeline.build-result/v1", "gaussian-pipeline.build-result/v2"], +) +def test_gateway_submits_tracks_and_imports_digest_bound_artifact( + tmp_path: Path, + result_schema: str, +) -> None: artifact = b"preview-sog" artifact_sha = hashlib.sha256(artifact).hexdigest() job_id = "gsp-20260825200000-deadbeef" @@ -375,7 +382,7 @@ def test_gateway_submits_tracks_and_imports_digest_bound_artifact(tmp_path: Path return httpx.Response( 200, json={ - "schema_version": "gaussian-pipeline.build-result/v1", + "schema_version": result_schema, "job_id": job_id, "runtime": { "source_revision": "a" * 40, diff --git a/tests/test_simulation_projects.py b/tests/test_simulation_projects.py index 81f9f55..5f73a77 100644 --- a/tests/test_simulation_projects.py +++ b/tests/test_simulation_projects.py @@ -16,6 +16,7 @@ from k1link.simulation.projects import ( SimulationProjectConflictError, SimulationProjectService, SimulationProjectStore, + _world_manifest, ) from k1link.web.simulation_projects_api import build_simulation_projects_router @@ -224,6 +225,27 @@ def test_service_materializes_world_manifest_and_deletes_both_copies(tmp_path: P assert provider.deleted == ["gsp-20260826000000-deadbeef"] +def test_world_manifest_prefers_seed_connected_rover_terrain() -> None: + manifest = _world_manifest( + "sim-" + "a" * 32, + [ + { + "role": "collision-mesh", + "logical_path": "collision/scene.collision.glb", + }, + { + "role": "rover-terrain-mesh", + "logical_path": "rover/terrain.collision.glb", + }, + ], + ) + + assert manifest["collision"]["available"] is True + assert manifest["collision"]["mesh_url"].endswith( + "/artifacts/rover/terrain.collision.glb" + ) + + def test_service_resumes_a_persisted_provider_job_without_reupload(tmp_path: Path) -> None: store = SimulationProjectStore(tmp_path) project = store.create(