diff --git a/internal/api/sample.go b/internal/api/sample.go
new file mode 100644
index 0000000..8093c93
--- /dev/null
+++ b/internal/api/sample.go
@@ -0,0 +1,43 @@
+package api
+
+import (
+ "net/http"
+ "os"
+ "path/filepath"
+)
+
+// The bundled "try our sample" artifact is the aegis security probe. It is
+// served publicly so the web UI empty state can drop it into the validate form
+// and run a real compatibility matrix. aegis uses a bloom-filter map
+// (BPF_MAP_TYPE_BLOOM_FILTER, kernel 5.16+), so it loads on >= 5.16 and fails
+// below — a precise, real compatibility boundary. See examples/aegis-live/.
+
+// sampleArtifactPath returns the path to the bundled sample .bpf.o, overridable
+// with BPFCOMPAT_SAMPLE_ARTIFACT for non-repo-root deployments (e.g. the demo).
+func sampleArtifactPath() string {
+ if p := os.Getenv("BPFCOMPAT_SAMPLE_ARTIFACT"); p != "" {
+ return p
+ }
+ return filepath.FromSlash("examples/aegis-live/aegis.bpf.o")
+}
+
+// handleSampleArtifact serves the bundled aegis sample object for the
+// "Try our aegis sample" empty-state button. Intentionally public (no auth):
+// it is a published sample artifact, not user data.
+func (s *Server) handleSampleArtifact(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodGet {
+ writeError(w, http.StatusMethodNotAllowed, "method not allowed")
+ return
+ }
+ // Fixed, operator-controlled sample path (default or BPFCOMPAT_SAMPLE_ARTIFACT).
+ path := filepath.Clean(sampleArtifactPath())
+ data, err := os.ReadFile(path) // #nosec G304 -- fixed sample path, not user input
+ if err != nil {
+ writeError(w, http.StatusNotFound, "sample artifact not available")
+ return
+ }
+ w.Header().Set("Content-Type", "application/octet-stream")
+ w.Header().Set("Content-Disposition", `attachment; filename="aegis.bpf.o"`)
+ w.Header().Set("Cache-Control", "public, max-age=3600")
+ _, _ = w.Write(data)
+}
diff --git a/internal/api/server.go b/internal/api/server.go
index b5f432f..219f061 100644
--- a/internal/api/server.go
+++ b/internal/api/server.go
@@ -488,6 +488,7 @@ func (s *Server) serve(ctx context.Context) error {
registerAPIRoute(mux, "/health", s.handleHealth)
registerAPIRoute(mux, "/config", s.handleConfig)
registerAPIRoute(mux, "/profiles", s.handleProfiles)
+ registerAPIRoute(mux, "/sample/aegis/artifact", s.handleSampleArtifact)
registerAPIRoute(mux, "/validate/start", s.handleValidateStart)
registerAPIRoute(mux, "/validate/status", s.handleValidateStatus)
registerAPIRoute(mux, "/validate", s.handleValidate)
diff --git a/internal/api/ui.go b/internal/api/ui.go
index cf7dd9c..2b2aea1 100644
--- a/internal/api/ui.go
+++ b/internal/api/ui.go
@@ -282,9 +282,8 @@ const uiHTML = `
.results {
padding: 12px 14px;
display: grid;
- grid-template-rows: auto auto auto 1fr auto;
+ grid-template-rows: repeat(5, auto);
gap: 10px;
- height: calc(100% - 52px);
box-sizing: border-box;
}
.progress-wrap {
@@ -854,6 +853,10 @@ const uiHTML = `
@@ -1766,6 +1769,59 @@ programs:
byId("modeSource").addEventListener("click", () => switchMode("source"));
byId("intentLoadAttach").addEventListener("click", () => switchTestIntent("load_attach"));
byId("intentLoadOnly").addEventListener("click", () => switchTestIntent("load_only"));
+
+ // "Try our aegis sample": load the bundled artifact into the form and run a
+ // real validation across a kernel spread that crosses aegis's 5.16 boundary
+ // (it uses a bloom-filter map). One click -> artifact filled below -> run.
+ byId("trySampleBtn").addEventListener("click", async () => {
+ const btn = byId("trySampleBtn");
+ const original = btn.textContent;
+ btn.disabled = true;
+ btn.textContent = "Loading aegis sample...";
+ try {
+ const res = await fetch("/api/v1/sample/aegis/artifact");
+ if (!res.ok) throw new Error("sample unavailable (HTTP " + res.status + ")");
+ const blob = await res.blob();
+ const file = new File([blob], "aegis.bpf.o", { type: "application/octet-stream" });
+ const dt = new DataTransfer();
+ dt.items.add(file);
+ const input = byId("artifactFile");
+ input.files = dt.files;
+ input.dispatchEvent(new Event("change", { bubbles: true }));
+ byId("artifactName").value = "aegis";
+
+ // Reset to a clean slate so only the boundary spread runs (no arm64 /
+ // kernel-sweep variants from the default selection).
+ document.querySelectorAll("input[data-kind='include']").forEach((x) => { x.checked = false; });
+ document.querySelectorAll("input[data-kind='required']").forEach((x) => { x.checked = false; x.disabled = true; });
+
+ // Boundary spread: 5.4/5.15 fail (no bloom map), 6.1/6.8 pass (required).
+ const include = ["ubuntu-20.04-5.4", "ubuntu-22.04-5.15", "debian-12-6.1", "ubuntu-24.04-6.8"];
+ const required = ["debian-12-6.1", "ubuntu-24.04-6.8"];
+ include.forEach((id) => {
+ const inc = document.querySelector("input[data-kind='include'][data-id='" + id + "']");
+ if (!inc || inc.disabled) return;
+ inc.checked = true;
+ inc.dispatchEvent(new Event("change", { bubbles: true }));
+ if (required.includes(id)) {
+ const req = document.querySelector("input[data-kind='required'][data-id='" + id + "']");
+ if (req) {
+ req.disabled = false;
+ req.checked = true;
+ req.dispatchEvent(new Event("change", { bubbles: true }));
+ }
+ }
+ });
+
+ btn.textContent = original;
+ btn.disabled = false;
+ byId("runBtn").click();
+ } catch (e) {
+ btn.textContent = original;
+ btn.disabled = false;
+ alert("Could not load the aegis sample: " + e.message);
+ }
+ });
runtimeModeButtons.probe.addEventListener("click", () => setRuntimeMode("probe"));
runtimeModeButtons.select.addEventListener("click", () => setRuntimeMode("select"));
runtimeModeButtons.fetch.addEventListener("click", () => setRuntimeMode("fetch"));
diff --git a/matrices/aegis-sample.yaml b/matrices/aegis-sample.yaml
new file mode 100644
index 0000000..cf87780
--- /dev/null
+++ b/matrices/aegis-sample.yaml
@@ -0,0 +1,20 @@
+name: aegis-sample
+# Boundary-spanning matrix for the "try our aegis sample" demo. aegis uses a BPF
+# bloom-filter map ("deny_bloom", BPF_MAP_TYPE_BLOOM_FILTER) that only exists on
+# kernel >= 5.16, so it exercises a precise, real compatibility boundary:
+# - ubuntu-20.04-5.4 : < 5.16 -> map create fails (UNSUPPORTED_MAP_TYPE) [expected]
+# - ubuntu-22.04-5.15: < 5.16 -> map create fails (UNSUPPORTED_MAP_TYPE) [expected]
+# - debian-12-6.1 : >= 5.16 -> loads + attaches -> pass
+# - ubuntu-24.04-6.8 : >= 5.16 -> loads + attaches -> pass
+# The < 5.16 rows are not "required": they are the teaching rows showing the
+# tool catching a genuine incompatibility, while the gate is green for the
+# kernels aegis actually supports.
+profiles:
+ - id: ubuntu-20.04-5.4
+ required: false
+ - id: ubuntu-22.04-5.15
+ required: false
+ - id: debian-12-6.1
+ required: true
+ - id: ubuntu-24.04-6.8
+ required: true