ocs_doc_api: fix branch-review findings (tab auth, bulge center, budget, caps, dead code)

CRITICAL:
- execute_doc_api: reject tab_id != bound tab (confused deputy); test
  doc_api_tab_mismatch_is_rejected.
- bulge_arc_segment: fix negative/CW bulge center (apothem magnitude + sign picks
  side; was double-sign-mirrored); test CW-vs-CCW center offset.
- cold-tess budget: move mass_cache/cold_tess_used to per-tab Scene so the budget
  persists across DocApi dispatches (was reset per request); budget-exhausted ->
  approximate meshes.metrics fallback instead of error.

WARNING:
- Loft: add BULK_ITEM_CAP (was uncapped -> unbounded kernel call from one request).
- CreateMany: pre-validate EntitySpec::Curve (ellipse ratio) in prepare phase ->
  all-or-nothing restored; regression test.
- Partial Ellipse: offset start/end_parameter by rot_z on rotation; regression test.
- resolve_body: add with_body borrow accessor (host overrides to borrow cache in
  place, O(1)); bounds() uses it instead of deep-cloning the B-rep per query.
- add_raster_image: validate file path (non-empty, image extension, reject
  UNC/traversal); regression test.

SUGGESTION:
- deploy: bump API_VERSION 5 -> 6 so DocApi plugins are gated at handshake on old
  hosts (append-only wire + defaulted method keep old V2-V5 plugins loading).
- dead code: wire query_name into apply_queries error labels; host uses crate's
  ObjectId::from_handle/to_handle (drop local duplicates); delete is_null/backend_mut.
- mock drift: test mock now stores curve specs + computes curve bounds (aligns with
  example mock); Spline/Dimension bounds delegate to points_aabb.

623 host lib + 8 crate + 3 consistency + 34 plugin_api green.
This commit is contained in:
Sebastian committed 2026-09-06 16:03:34 +02:00
1 parent ac54d6079b
commit 919128e087
10 files changed
+316 -71

No files matched your search

+43 -1
View File
@@ -12,6 +12,38 @@ use ocs_doc_api::{
HasId, ObjectId, PlacementSpec,
};
/// Coarse bounds for a stored 2D curve spec (mirrors the host's entity_bounds and
/// the example mock — all three must answer curve bounds identically).
fn curve_bounds(spec: &Curve2Spec) -> Aabb {
match spec {
Curve2Spec::Line { start, end } => Aabb {
min: [start[0].min(end[0]), start[1].min(end[1]), start[2].min(end[2])],
max: [start[0].max(end[0]), start[1].max(end[1]), start[2].max(end[2])],
},
Curve2Spec::Circle { centre, radius } | Curve2Spec::Arc { centre, radius, .. } => Aabb {
min: [centre[0] - radius, centre[1] - radius, centre[2]],
max: [centre[0] + radius, centre[1] + radius, centre[2]],
},
Curve2Spec::Point { position } => Aabb { min: *position, max: *position },
Curve2Spec::Polyline { points, .. } | Curve2Spec::Spline { control_points: points, .. } => {
let mut min = [f64::INFINITY; 3];
let mut max = [f64::NEG_INFINITY; 3];
for p in points {
for i in 0..3 {
min[i] = min[i].min(p[i]);
max[i] = max[i].max(p[i]);
}
}
Aabb { min, max }
}
Curve2Spec::Ellipse { centre, major_axis, .. } => {
let r = (major_axis[0] * major_axis[0] + major_axis[1] * major_axis[1] + major_axis[2] * major_axis[2]).sqrt();
Aabb { min: [centre[0] - r, centre[1] - r, centre[2] - r], max: [centre[0] + r, centre[1] + r, centre[2] + r] }
}
Curve2Spec::Ray { origin, .. } | Curve2Spec::XLine { origin, .. } => Aabb { min: *origin, max: *origin },
}
}
/// In-memory mock backend: HashMap<ObjectId, Body> + a tiny entity table.
/// Tracks undo steps + revision bumps to assert per-op semantics.
#[derive(Default)]
@@ -29,6 +61,9 @@ struct MockBackend {
dimension_measurements: HashMap<ObjectId, f64>,
/// Stored insert attributes.
attributes_store: HashMap<ObjectId, Vec<(String, String)>>,
/// Stored 2D curve specs so `bounds()` works on curve entities (aligned with
/// the example mock — both mocks must answer curve bounds the same way).
curves: HashMap<ObjectId, Curve2Spec>,
/// Stored viewport views (target, height).
viewport_views: HashMap<ObjectId, ([f64; 3], f64)>,
}
@@ -72,7 +107,9 @@ impl DocApiBackend for MockBackend {
Curve2Spec::Ray { .. } => "Ray",
Curve2Spec::XLine { .. } => "XLine",
};
Ok(self.alloc(kind))
let id = self.alloc(kind);
self.curves.insert(id, spec.clone());
Ok(id)
}
fn add_insert(&mut self, spec: &ocs_doc_api::ops::InsertSpec) -> ApiResult<ObjectId> {
if spec.block_name.is_empty() {
@@ -237,6 +274,11 @@ impl DocApiBackend for MockBackend {
Err(ApiError::Unsupported("profiles not mocked".into()))
}
fn bounds(&mut self, id: ObjectId) -> ApiResult<Aabb> {
// Solids via the kernel body; 2D curves from their stored spec (aligned
// with the example mock — both mocks answer curve bounds identically).
if let Some(spec) = self.curves.get(&id) {
return Ok(curve_bounds(spec));
}
let body = self.body(id)?;
let bb = cadkernel::brep::body_bounds(body)
.ok_or_else(|| ApiError::geometry(GeometryErrorKind::Empty, "no bounds"))?;