diff --git a/tests/kernel-cases/README.md b/tests/kernel-cases/README.md
index 709ec90c06..aeb5502466 100644
--- a/tests/kernel-cases/README.md
+++ b/tests/kernel-cases/README.md
@@ -37,6 +37,8 @@ One directory per area, one per case: `tests/kernel-cases///`.
| `crashes` (crashes and hangs inside kernel operations) | booleans/shell/import/crash rig |
| `fillets`, `sweeps`, `threads`, `sheet-metal`, `tessellation` | history rig |
| `continuity` (designed seams of known order, G0 to G4, that prove the continuity measurement itself) | history rig |
+| `solids` (cones, scale, align, bounding solids) | tool coverage |
+| `frames` (frame members, inserted profiles) | tool coverage |
Each area also has its gate files: `KNOWN_FAILING` and `PATHS` (see [the gate](#the-kernel-rig-gate)).
diff --git a/tests/kernel-cases/frames/KNOWN_FAILING b/tests/kernel-cases/frames/KNOWN_FAILING
new file mode 100644
index 0000000000..f1ab5c3aab
--- /dev/null
+++ b/tests/kernel-cases/frames/KNOWN_FAILING
@@ -0,0 +1,4 @@
+# The frames cases that fail today, one a line: ` `. Sorted. A run with
+# `--known-failing` fails on a case that fails and is not listed here, and on a listed case that
+# passes: a fix removes its line in the same pull request.
+# Baseline: rig-cases-solids-frames 45f69c0e5 with SMLib 60b1c6a, aarch64 Linux.
diff --git a/tests/kernel-cases/frames/PATHS b/tests/kernel-cases/frames/PATHS
new file mode 100644
index 0000000000..f09de15ae3
--- /dev/null
+++ b/tests/kernel-cases/frames/PATHS
@@ -0,0 +1,9 @@
+# Source files whose change must rerun the frames area (frame members, inserted profiles).
+apps/solidcraft-cli/src/kernel_cases.rs
+crates/doc/src/eval_frame.rs
+crates/doc/src/frame.rs
+crates/engine/src/cmd/frame.rs
+crates/engine/src/cmd/sketch.rs
+crates/kernel/src/**
+crates/smlib/**
+tests/kernel-cases/frames/**
diff --git a/tests/kernel-cases/frames/cov-frame-member-bar-along-x/expected.json b/tests/kernel-cases/frames/cov-frame-member-bar-along-x/expected.json
new file mode 100644
index 0000000000..1ad1fdad04
--- /dev/null
+++ b/tests/kernel-cases/frames/cov-frame-member-bar-along-x/expected.json
@@ -0,0 +1,22 @@
+{
+ "sources": [],
+ "what": "a 20x10 bar member along a 100 mm line on X",
+ "source": "analytic",
+ "method": "along +X the profile's y is world up (Z) and x = y x d = world Y; centred on the line (anchor origin = the bar's centre): x 0..100, y -10..10, z -5..5; V = 20000, A = 2(2000+1000+200)",
+ "body_count": 1,
+ "faces": 6,
+ "volume_mm3": 20000.0,
+ "area_mm2": 6400.0,
+ "bbox": {
+ "min": [
+ 0,
+ -10,
+ -5
+ ],
+ "max": [
+ 100,
+ 10,
+ 5
+ ]
+ }
+}
diff --git a/tests/kernel-cases/frames/cov-frame-member-bar-along-x/recipe.json b/tests/kernel-cases/frames/cov-frame-member-bar-along-x/recipe.json
new file mode 100644
index 0000000000..92e9fa692f
--- /dev/null
+++ b/tests/kernel-cases/frames/cov-frame-member-bar-along-x/recipe.json
@@ -0,0 +1,47 @@
+{
+ "commands": [
+ {
+ "command": "sketch.create",
+ "params": {
+ "plane": "XY",
+ "name": "Frame"
+ }
+ },
+ {
+ "command": "sketch.line",
+ "params": {
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 100,
+ 0
+ ]
+ ],
+ "ids": [
+ "l1"
+ ],
+ "infer": false
+ }
+ },
+ {
+ "command": "sketch.finish",
+ "params": {
+ "sketch": "Frame"
+ }
+ },
+ {
+ "command": "frame.member",
+ "params": {
+ "sketch": "Frame",
+ "profile": {
+ "kind": "bar",
+ "width": 20,
+ "height": 10
+ }
+ }
+ }
+ ]
+}
diff --git a/tests/kernel-cases/frames/cov-frame-member-tube-post-at-its-corner/expected.json b/tests/kernel-cases/frames/cov-frame-member-tube-post-at-its-corner/expected.json
new file mode 100644
index 0000000000..7960647301
--- /dev/null
+++ b/tests/kernel-cases/frames/cov-frame-member-tube-post-at-its-corner/expected.json
@@ -0,0 +1,21 @@
+{
+ "sources": [],
+ "what": "a vertical rectangular tube 40x30x3 (sharp corners) on a 50 mm line, anchored at the profile's bottom left corner",
+ "source": "analytic",
+ "method": "a vertical member's x is world X, y = d x X = world Y; the bottom left corner on the line: x 0..40, y 0..30, z 0..50; section 40*30 - 34*24 = 384, V = 19200; faces 4 outside + 4 inside + 2 ends",
+ "body_count": 1,
+ "faces": 10,
+ "volume_mm3": 19200.0,
+ "bbox": {
+ "min": [
+ 0,
+ 0,
+ 0
+ ],
+ "max": [
+ 40,
+ 30,
+ 50
+ ]
+ }
+}
diff --git a/tests/kernel-cases/frames/cov-frame-member-tube-post-at-its-corner/recipe.json b/tests/kernel-cases/frames/cov-frame-member-tube-post-at-its-corner/recipe.json
new file mode 100644
index 0000000000..bb1f4333b0
--- /dev/null
+++ b/tests/kernel-cases/frames/cov-frame-member-tube-post-at-its-corner/recipe.json
@@ -0,0 +1,66 @@
+{
+ "commands": [
+ {
+ "command": "sketch.create",
+ "params": {
+ "plane": {
+ "origin": [
+ 0,
+ 0,
+ 0
+ ],
+ "x_dir": [
+ 1,
+ 0,
+ 0
+ ],
+ "y_dir": [
+ 0,
+ 0,
+ 1
+ ]
+ },
+ "name": "Post"
+ }
+ },
+ {
+ "command": "sketch.line",
+ "params": {
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 50
+ ]
+ ],
+ "ids": [
+ "l1"
+ ],
+ "infer": false
+ }
+ },
+ {
+ "command": "sketch.finish",
+ "params": {
+ "sketch": "Post"
+ }
+ },
+ {
+ "command": "frame.member",
+ "params": {
+ "sketch": "Post",
+ "profile": {
+ "kind": "rect_tube",
+ "width": 40,
+ "height": 30,
+ "wall": 3,
+ "outer_radius": 0
+ },
+ "anchor": "bottom_left"
+ }
+ }
+ ]
+}
diff --git a/tests/kernel-cases/frames/cov-frame-member-tube-wall-too-thick-refused/expected.json b/tests/kernel-cases/frames/cov-frame-member-tube-wall-too-thick-refused/expected.json
new file mode 100644
index 0000000000..3d775625d3
--- /dev/null
+++ b/tests/kernel-cases/frames/cov-frame-member-tube-wall-too-thick-refused/expected.json
@@ -0,0 +1,8 @@
+{
+ "sources": [],
+ "what": "a 40x40 tube with a 20 mm wall has no hole: refused",
+ "source": "analytic",
+ "method": "the wall must be under half the smaller side (20): a clean refusal naming it",
+ "outcome": "refused",
+ "refusal": "must be less than half the smaller side"
+}
diff --git a/tests/kernel-cases/frames/cov-frame-member-tube-wall-too-thick-refused/recipe.json b/tests/kernel-cases/frames/cov-frame-member-tube-wall-too-thick-refused/recipe.json
new file mode 100644
index 0000000000..0da9066ceb
--- /dev/null
+++ b/tests/kernel-cases/frames/cov-frame-member-tube-wall-too-thick-refused/recipe.json
@@ -0,0 +1,48 @@
+{
+ "commands": [
+ {
+ "command": "sketch.create",
+ "params": {
+ "plane": "XY",
+ "name": "Frame"
+ }
+ },
+ {
+ "command": "sketch.line",
+ "params": {
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 100,
+ 0
+ ]
+ ],
+ "ids": [
+ "l1"
+ ],
+ "infer": false
+ }
+ },
+ {
+ "command": "sketch.finish",
+ "params": {
+ "sketch": "Frame"
+ }
+ },
+ {
+ "command": "frame.member",
+ "params": {
+ "sketch": "Frame",
+ "profile": {
+ "kind": "rect_tube",
+ "width": 40,
+ "height": 40,
+ "wall": 20
+ }
+ }
+ }
+ ]
+}
diff --git a/tests/kernel-cases/frames/cov-frame-member-two-lines-two-bodies/expected.json b/tests/kernel-cases/frames/cov-frame-member-two-lines-two-bodies/expected.json
new file mode 100644
index 0000000000..1a7ec0eff6
--- /dev/null
+++ b/tests/kernel-cases/frames/cov-frame-member-two-lines-two-bodies/expected.json
@@ -0,0 +1,20 @@
+{
+ "sources": [],
+ "what": "round bar members d10 on two lines meeting at a corner: one body per line",
+ "source": "analytic",
+ "method": "each member a cylinder r5 the line's length: 25 pi (100 + 50); two bodies (they overlap at the corner, kept apart)",
+ "body_count": 2,
+ "volume_mm3": 11780.972450961724,
+ "bbox": {
+ "min": [
+ -5,
+ -5,
+ -5
+ ],
+ "max": [
+ 100,
+ 50,
+ 5
+ ]
+ }
+}
diff --git a/tests/kernel-cases/frames/cov-frame-member-two-lines-two-bodies/recipe.json b/tests/kernel-cases/frames/cov-frame-member-two-lines-two-bodies/recipe.json
new file mode 100644
index 0000000000..ce4e42bf23
--- /dev/null
+++ b/tests/kernel-cases/frames/cov-frame-member-two-lines-two-bodies/recipe.json
@@ -0,0 +1,65 @@
+{
+ "commands": [
+ {
+ "command": "sketch.create",
+ "params": {
+ "plane": "XY",
+ "name": "Frame"
+ }
+ },
+ {
+ "command": "sketch.line",
+ "params": {
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 100,
+ 0
+ ]
+ ],
+ "ids": [
+ "l1"
+ ],
+ "infer": false
+ }
+ },
+ {
+ "command": "sketch.line",
+ "params": {
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 50
+ ]
+ ],
+ "ids": [
+ "l2"
+ ],
+ "infer": false
+ }
+ },
+ {
+ "command": "sketch.finish",
+ "params": {
+ "sketch": "Frame"
+ }
+ },
+ {
+ "command": "frame.member",
+ "params": {
+ "sketch": "Frame",
+ "profile": {
+ "kind": "round_bar",
+ "diameter": 10
+ }
+ }
+ }
+ ]
+}
diff --git a/tests/kernel-cases/frames/cov-frame-profile-insert-channel-anchored-and-turned/expected.json b/tests/kernel-cases/frames/cov-frame-profile-insert-channel-anchored-and-turned/expected.json
new file mode 100644
index 0000000000..26fe9f9dfe
--- /dev/null
+++ b/tests/kernel-cases/frames/cov-frame-profile-insert-channel-anchored-and-turned/expected.json
@@ -0,0 +1,21 @@
+{
+ "sources": [],
+ "what": "a channel profile 60x30 (web 5, flanges 6, sharp) inserted at (10,10) by its bottom left corner and turned 90 deg",
+ "source": "analytic",
+ "method": "section 2*30*6 + 5*48 = 600, V = 12000 at 20 deep; bounding box 30 wide, 60 high from its corner, turned 90 deg ccw about it: x 10-60..10, y 10..40",
+ "body_count": 1,
+ "faces": 10,
+ "volume_mm3": 12000.0,
+ "bbox": {
+ "min": [
+ -50,
+ 10,
+ 0
+ ],
+ "max": [
+ 10,
+ 40,
+ 20
+ ]
+ }
+}
diff --git a/tests/kernel-cases/frames/cov-frame-profile-insert-channel-anchored-and-turned/recipe.json b/tests/kernel-cases/frames/cov-frame-profile-insert-channel-anchored-and-turned/recipe.json
new file mode 100644
index 0000000000..912be5bd3e
--- /dev/null
+++ b/tests/kernel-cases/frames/cov-frame-profile-insert-channel-anchored-and-turned/recipe.json
@@ -0,0 +1,44 @@
+{
+ "commands": [
+ {
+ "command": "sketch.create",
+ "params": {
+ "plane": "XY",
+ "name": "Section"
+ }
+ },
+ {
+ "command": "frame.profile.insert",
+ "params": {
+ "profile": {
+ "kind": "channel",
+ "height": 60,
+ "width": 30,
+ "web": 5,
+ "flange": 6,
+ "root_radius": 0,
+ "toe_radius": 0
+ },
+ "at": [
+ 10,
+ 10
+ ],
+ "anchor": "bottom_left",
+ "angle": 1.5707963267948966
+ }
+ },
+ {
+ "command": "sketch.finish",
+ "params": {
+ "sketch": "Section"
+ }
+ },
+ {
+ "command": "solid.extrude",
+ "params": {
+ "sketch": "Section",
+ "distance": 20
+ }
+ }
+ ]
+}
diff --git a/tests/kernel-cases/frames/cov-frame-profile-insert-i-beam-extruded/expected.json b/tests/kernel-cases/frames/cov-frame-profile-insert-i-beam-extruded/expected.json
new file mode 100644
index 0000000000..79c717bd15
--- /dev/null
+++ b/tests/kernel-cases/frames/cov-frame-profile-insert-i-beam-extruded/expected.json
@@ -0,0 +1,21 @@
+{
+ "sources": [],
+ "what": "an I-beam profile (100x50, web 4.5, flanges 7, sharp roots) inserted into a sketch and extruded 100",
+ "source": "analytic",
+ "method": "section 2*50*7 + 4.5*(100-14) = 1087, centred on the origin: V = 108700; faces 12 sides + 2 ends",
+ "body_count": 1,
+ "faces": 14,
+ "volume_mm3": 108700.0,
+ "bbox": {
+ "min": [
+ -25,
+ -50,
+ 0
+ ],
+ "max": [
+ 25,
+ 50,
+ 100
+ ]
+ }
+}
diff --git a/tests/kernel-cases/frames/cov-frame-profile-insert-i-beam-extruded/recipe.json b/tests/kernel-cases/frames/cov-frame-profile-insert-i-beam-extruded/recipe.json
new file mode 100644
index 0000000000..b206d837f2
--- /dev/null
+++ b/tests/kernel-cases/frames/cov-frame-profile-insert-i-beam-extruded/recipe.json
@@ -0,0 +1,37 @@
+{
+ "commands": [
+ {
+ "command": "sketch.create",
+ "params": {
+ "plane": "XY",
+ "name": "Section"
+ }
+ },
+ {
+ "command": "frame.profile.insert",
+ "params": {
+ "profile": {
+ "kind": "i_beam",
+ "height": 100,
+ "width": 50,
+ "web": 4.5,
+ "flange": 7,
+ "root_radius": 0
+ }
+ }
+ },
+ {
+ "command": "sketch.finish",
+ "params": {
+ "sketch": "Section"
+ }
+ },
+ {
+ "command": "solid.extrude",
+ "params": {
+ "sketch": "Section",
+ "distance": 100
+ }
+ }
+ ]
+}