detached 1dd2cab

fix(startup): externalize comparison evidence

commit
1dd2cab14bbe684b30ed206d29ed4ee3a6d52e4e
author
brice · · 2026-07-14 09:53 UTC
parent
a948576
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +114 -19
M docs/first-presentation.md +18 -9
@@ -25,17 +25,26 @@ Build the candidate, then run:
2525
2626 ```bash
2727 DUMBER_FIRST_PRESENTATION_BIN="$PWD/dist/dumber" \
28-DUMBER_FIRST_PRESENTATION_OUTPUT="$PWD/phase1/first-presentation-$(date +%s)" \
28+DUMBER_MACHINE_GPU_PROFILE=integrated-gpu \
2929 scripts/collect_first_presentation.sh
3030 ```
3131
32-The output directory must be a new, absolute directory below an existing
33-non-symlink parent. The collector refuses to clear or reuse a caller-supplied
34-path; review and publish a fresh collection explicitly. The script requires the current display and the CEF 147 runtime at
32+By default each collection is a fresh directory below
33+`$XDG_STATE_HOME/dumber/roadmap-evidence` (or
34+`$HOME/.local/state/dumber/roadmap-evidence` when `XDG_STATE_HOME` is unset),
35+not in the repository. `DUMBER_FIRST_PRESENTATION_OUTPUT` may override it only
36+with a new absolute directory below an existing non-symlink parent. The
37+collector never clears or reuses a caller-supplied path.
38+
39+The script requires the current display and the CEF 147 runtime at
3540 `~/.local/share/cef-147-runtime` (override with `DUMBER_CEF_DIR`). It performs
3641 exactly five bounded launches with fresh XDG homes and CEF root cache, fixes the
37-DMABUF/Vulkan environment, and writes `metadata.json`, `run-01.json` through
38-`run-05.json`, and `baseline.json` below `phase1/first-presentation`. Published
39-artifacts contain only logical labels, version/hash identifiers, and timing
40-fields; raw logs and temporary XDG homes are removed. A missing or incomplete
41-timeline, non-DMABUF backend, or invalid run fails collection.
42+DMABUF/Vulkan renderer, and writes exactly `metadata.json`, `run-01.json`
43+through `run-05.json`, and `baseline.json`. Metadata includes only coarse,
44+comparison-safe OS/architecture, display protocol, and machine/GPU profile
45+labels. Set `DUMBER_MACHINE_GPU_PROFILE` to one of `generic-gpu`,
46+`integrated-gpu`, `discrete-gpu`, `hybrid-gpu`, `virtual-gpu`, or `unknown-gpu`;
47+do not use a device model or other identifier. Publish only the seven reviewed
48+JSON files to an external Gist. Raw logs and temporary XDG homes are removed and
49+must not be published. A missing or incomplete timeline, non-DMABUF backend, or
50+invalid run fails collection.
@@ -25,17 +25,26 @@ Build the candidate, then run:
25 25
26 ```bash26 ```bash
27 DUMBER_FIRST_PRESENTATION_BIN="$PWD/dist/dumber" \27 DUMBER_FIRST_PRESENTATION_BIN="$PWD/dist/dumber" \
28-DUMBER_FIRST_PRESENTATION_OUTPUT="$PWD/phase1/first-presentation-$(date +%s)" \28+DUMBER_MACHINE_GPU_PROFILE=integrated-gpu \
29 scripts/collect_first_presentation.sh29 scripts/collect_first_presentation.sh
30 ```30 ```
31 31
32-The output directory must be a new, absolute directory below an existing32+By default each collection is a fresh directory below
33-non-symlink parent. The collector refuses to clear or reuse a caller-supplied33+`$XDG_STATE_HOME/dumber/roadmap-evidence` (or
34-path; review and publish a fresh collection explicitly. The script requires the current display and the CEF 147 runtime at34+`$HOME/.local/state/dumber/roadmap-evidence` when `XDG_STATE_HOME` is unset),
35+not in the repository. `DUMBER_FIRST_PRESENTATION_OUTPUT` may override it only
36+with a new absolute directory below an existing non-symlink parent. The
37+collector never clears or reuses a caller-supplied path.
38+
39+The script requires the current display and the CEF 147 runtime at
35 `~/.local/share/cef-147-runtime` (override with `DUMBER_CEF_DIR`). It performs40 `~/.local/share/cef-147-runtime` (override with `DUMBER_CEF_DIR`). It performs
36 exactly five bounded launches with fresh XDG homes and CEF root cache, fixes the41 exactly five bounded launches with fresh XDG homes and CEF root cache, fixes the
37-DMABUF/Vulkan environment, and writes `metadata.json`, `run-01.json` through42+DMABUF/Vulkan renderer, and writes exactly `metadata.json`, `run-01.json`
38-`run-05.json`, and `baseline.json` below `phase1/first-presentation`. Published43+through `run-05.json`, and `baseline.json`. Metadata includes only coarse,
39-artifacts contain only logical labels, version/hash identifiers, and timing44+comparison-safe OS/architecture, display protocol, and machine/GPU profile
40-fields; raw logs and temporary XDG homes are removed. A missing or incomplete45+labels. Set `DUMBER_MACHINE_GPU_PROFILE` to one of `generic-gpu`,
41-timeline, non-DMABUF backend, or invalid run fails collection.46+`integrated-gpu`, `discrete-gpu`, `hybrid-gpu`, `virtual-gpu`, or `unknown-gpu`;
47+do not use a device model or other identifier. Publish only the seven reviewed
48+JSON files to an external Gist. Raw logs and temporary XDG homes are removed and
49+must not be published. A missing or incomplete timeline, non-DMABUF backend, or
50+invalid run fails collection.
M internal/logging/first_presentation_collector_test.go +62 -0
@@ -122,10 +122,12 @@ func TestFirstPresentationCollectorSanitizesMachineLocalValues(t *testing.T) {
122122 cmd.Dir = repoRoot
123123 cmd.Env = append(os.Environ(),
124124 "DISPLAY=:test",
125+ "WAYLAND_DISPLAY=",
125126 "DUMBER_CEF_DIR="+runtime,
126127 "DUMBER_FIRST_PRESENTATION_BIN="+binary,
127128 "DUMBER_FIRST_PRESENTATION_OUTPUT="+output,
128129 "DUMBER_FIRST_PRESENTATION_TIMEOUT_SECONDS=1",
130+ "DUMBER_MACHINE_GPU_PROFILE=integrated-gpu",
129131 )
130132 result, err := cmd.CombinedOutput()
131133 require.NoErrorf(t, err, "collector failed: %s", result)
@@ -146,6 +148,66 @@ func TestFirstPresentationCollectorSanitizesMachineLocalValues(t *testing.T) {
146148 } {
147149 require.Contains(t, artifacts.String(), required)
148150 }
151+
152+ var metadata struct {
153+ Comparison struct {
154+ OS string `json:"os"`
155+ Architecture string `json:"architecture"`
156+ DisplayProtocol string `json:"display_protocol"`
157+ MachineGPUProfile string `json:"machine_gpu_profile"`
158+ } `json:"comparison"`
159+ }
160+ contents, err := os.ReadFile(filepath.Join(output, "metadata.json"))
161+ require.NoError(t, err)
162+ require.NoError(t, json.Unmarshal(contents, &metadata))
163+ require.NotEmpty(t, metadata.Comparison.OS)
164+ require.NotEmpty(t, metadata.Comparison.Architecture)
165+ require.Equal(t, "x11", metadata.Comparison.DisplayProtocol)
166+ require.Equal(t, "integrated-gpu", metadata.Comparison.MachineGPUProfile)
167+}
168+
169+func TestFirstPresentationCollectorDefaultsToXDGStateEvidenceDirectory(t *testing.T) {
170+ repoRoot, err := filepath.Abs(filepath.Join("..", ".."))
171+ require.NoError(t, err)
172+ temp := t.TempDir()
173+ runtime := filepath.Join(temp, "cef-147-runtime")
174+ require.NoError(t, os.Mkdir(runtime, 0o755))
175+ binary := filepath.Join(temp, "dumber")
176+ require.NoError(t, os.WriteFile(binary, collectorTestBinary(validFirstPresentationLog), 0o755))
177+ stateHome := filepath.Join(temp, "state")
178+
179+ cmd := exec.Command(filepath.Join(repoRoot, "scripts", "collect_first_presentation.sh"))
180+ cmd.Dir = repoRoot
181+ cmd.Env = append(envWithout("DUMBER_FIRST_PRESENTATION_OUTPUT"),
182+ "DISPLAY=:test",
183+ "WAYLAND_DISPLAY=",
184+ "DUMBER_CEF_DIR="+runtime,
185+ "DUMBER_FIRST_PRESENTATION_BIN="+binary,
186+ "DUMBER_FIRST_PRESENTATION_TIMEOUT_SECONDS=1",
187+ "DUMBER_MACHINE_GPU_PROFILE=integrated-gpu",
188+ "XDG_STATE_HOME="+stateHome,
189+ )
190+ result, err := cmd.CombinedOutput()
191+ require.NoErrorf(t, err, "collector failed: %s", result)
192+
193+ evidenceRoot := filepath.Join(stateHome, "dumber", "roadmap-evidence")
194+ entries, err := os.ReadDir(evidenceRoot)
195+ require.NoError(t, err)
196+ require.Len(t, entries, 1)
197+ require.True(t, entries[0].IsDir())
198+ require.FileExists(t, filepath.Join(evidenceRoot, entries[0].Name(), "metadata.json"))
199+ require.NotContains(t, string(result), filepath.Join(repoRoot, "phase1"))
200+}
201+
202+func envWithout(name string) []string {
203+ prefix := name + "="
204+ var environment []string
205+ for _, entry := range os.Environ() {
206+ if !strings.HasPrefix(entry, prefix) {
207+ environment = append(environment, entry)
208+ }
209+ }
210+ return environment
149211 }
150212
151213 func TestFirstPresentationCollectorReadsProvenanceWithScopedGitSafeDirectory(t *testing.T) {
@@ -122,10 +122,12 @@ func TestFirstPresentationCollectorSanitizesMachineLocalValues(t *testing.T) {
122 cmd.Dir = repoRoot122 cmd.Dir = repoRoot
123 cmd.Env = append(os.Environ(),123 cmd.Env = append(os.Environ(),
124 "DISPLAY=:test",124 "DISPLAY=:test",
125+ "WAYLAND_DISPLAY=",
125 "DUMBER_CEF_DIR="+runtime,126 "DUMBER_CEF_DIR="+runtime,
126 "DUMBER_FIRST_PRESENTATION_BIN="+binary,127 "DUMBER_FIRST_PRESENTATION_BIN="+binary,
127 "DUMBER_FIRST_PRESENTATION_OUTPUT="+output,128 "DUMBER_FIRST_PRESENTATION_OUTPUT="+output,
128 "DUMBER_FIRST_PRESENTATION_TIMEOUT_SECONDS=1",129 "DUMBER_FIRST_PRESENTATION_TIMEOUT_SECONDS=1",
130+ "DUMBER_MACHINE_GPU_PROFILE=integrated-gpu",
129 )131 )
130 result, err := cmd.CombinedOutput()132 result, err := cmd.CombinedOutput()
131 require.NoErrorf(t, err, "collector failed: %s", result)133 require.NoErrorf(t, err, "collector failed: %s", result)
@@ -146,6 +148,66 @@ func TestFirstPresentationCollectorSanitizesMachineLocalValues(t *testing.T) {
146 } {148 } {
147 require.Contains(t, artifacts.String(), required)149 require.Contains(t, artifacts.String(), required)
148 }150 }
151+
152+ var metadata struct {
153+ Comparison struct {
154+ OS string `json:"os"`
155+ Architecture string `json:"architecture"`
156+ DisplayProtocol string `json:"display_protocol"`
157+ MachineGPUProfile string `json:"machine_gpu_profile"`
158+ } `json:"comparison"`
159+ }
160+ contents, err := os.ReadFile(filepath.Join(output, "metadata.json"))
161+ require.NoError(t, err)
162+ require.NoError(t, json.Unmarshal(contents, &metadata))
163+ require.NotEmpty(t, metadata.Comparison.OS)
164+ require.NotEmpty(t, metadata.Comparison.Architecture)
165+ require.Equal(t, "x11", metadata.Comparison.DisplayProtocol)
166+ require.Equal(t, "integrated-gpu", metadata.Comparison.MachineGPUProfile)
167+}
168+
169+func TestFirstPresentationCollectorDefaultsToXDGStateEvidenceDirectory(t *testing.T) {
170+ repoRoot, err := filepath.Abs(filepath.Join("..", ".."))
171+ require.NoError(t, err)
172+ temp := t.TempDir()
173+ runtime := filepath.Join(temp, "cef-147-runtime")
174+ require.NoError(t, os.Mkdir(runtime, 0o755))
175+ binary := filepath.Join(temp, "dumber")
176+ require.NoError(t, os.WriteFile(binary, collectorTestBinary(validFirstPresentationLog), 0o755))
177+ stateHome := filepath.Join(temp, "state")
178+
179+ cmd := exec.Command(filepath.Join(repoRoot, "scripts", "collect_first_presentation.sh"))
180+ cmd.Dir = repoRoot
181+ cmd.Env = append(envWithout("DUMBER_FIRST_PRESENTATION_OUTPUT"),
182+ "DISPLAY=:test",
183+ "WAYLAND_DISPLAY=",
184+ "DUMBER_CEF_DIR="+runtime,
185+ "DUMBER_FIRST_PRESENTATION_BIN="+binary,
186+ "DUMBER_FIRST_PRESENTATION_TIMEOUT_SECONDS=1",
187+ "DUMBER_MACHINE_GPU_PROFILE=integrated-gpu",
188+ "XDG_STATE_HOME="+stateHome,
189+ )
190+ result, err := cmd.CombinedOutput()
191+ require.NoErrorf(t, err, "collector failed: %s", result)
192+
193+ evidenceRoot := filepath.Join(stateHome, "dumber", "roadmap-evidence")
194+ entries, err := os.ReadDir(evidenceRoot)
195+ require.NoError(t, err)
196+ require.Len(t, entries, 1)
197+ require.True(t, entries[0].IsDir())
198+ require.FileExists(t, filepath.Join(evidenceRoot, entries[0].Name(), "metadata.json"))
199+ require.NotContains(t, string(result), filepath.Join(repoRoot, "phase1"))
200+}
201+
202+func envWithout(name string) []string {
203+ prefix := name + "="
204+ var environment []string
205+ for _, entry := range os.Environ() {
206+ if !strings.HasPrefix(entry, prefix) {
207+ environment = append(environment, entry)
208+ }
209+ }
210+ return environment
149 }211 }
150 212
151 func TestFirstPresentationCollectorReadsProvenanceWithScopedGitSafeDirectory(t *testing.T) {213 func TestFirstPresentationCollectorReadsProvenanceWithScopedGitSafeDirectory(t *testing.T) {
M scripts/collect_first_presentation.sh +34 -10
@@ -8,21 +8,32 @@ readonly runs=5
88 readonly timeout_seconds="${DUMBER_FIRST_PRESENTATION_TIMEOUT_SECONDS:-45}"
99 readonly runtime="${DUMBER_CEF_DIR:-$HOME/.local/share/cef-147-runtime}"
1010 readonly binary="${DUMBER_FIRST_PRESENTATION_BIN:-$PWD/dist/dumber}"
11+
12+fail_unsafe_output() {
13+ echo "first-presentation: unsafe output path: $1" >&2
14+ exit 2
15+}
16+
1117 if [[ -v DUMBER_FIRST_PRESENTATION_OUTPUT ]]; then
1218 output="$DUMBER_FIRST_PRESENTATION_OUTPUT"
1319 else
14- output="$PWD/phase1/first-presentation"
20+ # Evidence is state, not source: keep the default outside the checkout and
21+ # make every invocation a fresh child of the external evidence directory.
22+ readonly state_home="${XDG_STATE_HOME:-$HOME/.local/state}"
23+ [[ "$state_home" == /* ]] || fail_unsafe_output "XDG_STATE_HOME must be absolute"
24+ readonly evidence_root="$state_home/dumber/roadmap-evidence"
25+ mkdir -p -- "$evidence_root" || fail_unsafe_output "could not create XDG state directory"
26+ canonical_evidence_root="$(realpath -e -- "$evidence_root")" || \
27+ fail_unsafe_output "XDG state directory cannot be canonicalized"
28+ [[ "$canonical_evidence_root" == "$evidence_root" && ! -L "$evidence_root" ]] || \
29+ fail_unsafe_output "XDG state directory must not contain symbolic links"
30+ output="$evidence_root/first-presentation-$(date -u +%Y%m%dT%H%M%S)-$$"
1531 fi
1632 readonly output
1733 readonly upstream_module="github.com/bnema/purego-cef2gtk"
1834 readonly upstream_tag="v0.8.4"
1935 readonly upstream_revision="f217ece342dea3ef2a3f98671fcd16a39ad0037d"
2036
21-fail_unsafe_output() {
22- echo "first-presentation: unsafe output path: $1" >&2
23- exit 2
24-}
25-
2637 # The artifact destination is caller-controlled. Never empty or recursively
2738 # clear it: collection only writes to a newly-created directory whose parent is
2839 # already canonical and contains no symbolic-link hop.
@@ -62,25 +73,38 @@ prepare_output
6273
6374 # Raw logs and temporary XDG homes may contain machine-local paths. Keep them
6475 # outside the committed artifact directory and always remove them.
65-readonly work_root="$(mktemp -d "${TMPDIR:-/tmp}/dumber-first-presentation.XXXXXX")"
76+work_root="$(mktemp -d "${TMPDIR:-/tmp}/dumber-first-presentation.XXXXXX")"
77+readonly work_root
6678 cleanup_work_root() {
6779 [[ -n "${work_root:-}" && -d "$work_root" && ! -L "$work_root" ]] || return
6880 rm -rf -- "$work_root"
6981 }
7082 trap cleanup_work_root EXIT
7183 python3 - "$output/metadata.json" "$binary" "$timeout_seconds" "$upstream_module" "$upstream_tag" "$upstream_revision" <<'PY'
72-import hashlib, json, os, subprocess, sys
84+import hashlib, json, os, platform, subprocess, sys
7385 path, binary, timeout, upstream_module, upstream_tag, upstream_revision = sys.argv[1:]
7486 with open(binary, "rb") as candidate:
7587 binary_sha256 = hashlib.file_digest(candidate, "sha256").hexdigest()
88+
89+# These deliberately coarse labels support comparison without exposing a host,
90+# device name, driver version, path, or environment dump.
91+architecture = {"x86_64": "amd64", "amd64": "amd64", "aarch64": "arm64", "arm64": "arm64"}.get(platform.machine().lower(), "other")
92+os_label = {"linux": "linux", "darwin": "darwin", "windows": "windows"}.get(platform.system().lower(), "other")
93+display_protocol = "wayland" if os.environ.get("WAYLAND_DISPLAY") else "x11"
94+gpu_profile = os.environ.get("DUMBER_MACHINE_GPU_PROFILE", "generic-gpu")
95+allowed_gpu_profiles = {"generic-gpu", "integrated-gpu", "discrete-gpu", "hybrid-gpu", "virtual-gpu", "unknown-gpu"}
96+if gpu_profile not in allowed_gpu_profiles:
97+ raise SystemExit("DUMBER_MACHINE_GPU_PROFILE must be a non-identifying profile label")
98+
7699 json.dump({
77100 "runs": 5,
78- "runtime": {"label": "cef", "version": os.environ.get("DUMBER_CEF_RUNTIME_VERSION", "147")},
101+ "runtime": {"label": "cef", "version": "147"},
79102 "binary": {"label": "dumber", "sha256": binary_sha256},
80103 "timeout_seconds": int(timeout),
81104 "measured_source_revision": subprocess.check_output(["git", "-c", f"safe.directory={os.getcwd()}", "rev-parse", "HEAD"], text=True).strip(),
82105 "upstream": {"module": upstream_module, "tag": upstream_tag, "revision": upstream_revision},
83- "fixed_environment": {"DUMBER_RENDER_STACK": "vulkan-dmabuf", "PUREGO_CEF2GTK_BACKEND": "gdk-dmabuf", "GSK_RENDERER": "vulkan"}
106+ "comparison": {"os": os_label, "architecture": architecture, "display_protocol": display_protocol, "machine_gpu_profile": gpu_profile},
107+ "render_configuration": {"backend": "gdk-dmabuf", "buffer_sharing": "dmabuf", "renderer": "vulkan"}
84108 }, open(path, "w"), indent=2, sort_keys=True)
85109 PY
86110
@@ -8,21 +8,32 @@ readonly runs=5
8 readonly timeout_seconds="${DUMBER_FIRST_PRESENTATION_TIMEOUT_SECONDS:-45}"8 readonly timeout_seconds="${DUMBER_FIRST_PRESENTATION_TIMEOUT_SECONDS:-45}"
9 readonly runtime="${DUMBER_CEF_DIR:-$HOME/.local/share/cef-147-runtime}"9 readonly runtime="${DUMBER_CEF_DIR:-$HOME/.local/share/cef-147-runtime}"
10 readonly binary="${DUMBER_FIRST_PRESENTATION_BIN:-$PWD/dist/dumber}"10 readonly binary="${DUMBER_FIRST_PRESENTATION_BIN:-$PWD/dist/dumber}"
11+
12+fail_unsafe_output() {
13+ echo "first-presentation: unsafe output path: $1" >&2
14+ exit 2
15+}
16+
11 if [[ -v DUMBER_FIRST_PRESENTATION_OUTPUT ]]; then17 if [[ -v DUMBER_FIRST_PRESENTATION_OUTPUT ]]; then
12 output="$DUMBER_FIRST_PRESENTATION_OUTPUT"18 output="$DUMBER_FIRST_PRESENTATION_OUTPUT"
13 else19 else
14- output="$PWD/phase1/first-presentation"20+ # Evidence is state, not source: keep the default outside the checkout and
21+ # make every invocation a fresh child of the external evidence directory.
22+ readonly state_home="${XDG_STATE_HOME:-$HOME/.local/state}"
23+ [[ "$state_home" == /* ]] || fail_unsafe_output "XDG_STATE_HOME must be absolute"
24+ readonly evidence_root="$state_home/dumber/roadmap-evidence"
25+ mkdir -p -- "$evidence_root" || fail_unsafe_output "could not create XDG state directory"
26+ canonical_evidence_root="$(realpath -e -- "$evidence_root")" || \
27+ fail_unsafe_output "XDG state directory cannot be canonicalized"
28+ [[ "$canonical_evidence_root" == "$evidence_root" && ! -L "$evidence_root" ]] || \
29+ fail_unsafe_output "XDG state directory must not contain symbolic links"
30+ output="$evidence_root/first-presentation-$(date -u +%Y%m%dT%H%M%S)-$$"
15 fi31 fi
16 readonly output32 readonly output
17 readonly upstream_module="github.com/bnema/purego-cef2gtk"33 readonly upstream_module="github.com/bnema/purego-cef2gtk"
18 readonly upstream_tag="v0.8.4"34 readonly upstream_tag="v0.8.4"
19 readonly upstream_revision="f217ece342dea3ef2a3f98671fcd16a39ad0037d"35 readonly upstream_revision="f217ece342dea3ef2a3f98671fcd16a39ad0037d"
20 36
21-fail_unsafe_output() {
22- echo "first-presentation: unsafe output path: $1" >&2
23- exit 2
24-}
25-
26 # The artifact destination is caller-controlled. Never empty or recursively37 # The artifact destination is caller-controlled. Never empty or recursively
27 # clear it: collection only writes to a newly-created directory whose parent is38 # clear it: collection only writes to a newly-created directory whose parent is
28 # already canonical and contains no symbolic-link hop.39 # already canonical and contains no symbolic-link hop.
@@ -62,25 +73,38 @@ prepare_output
62 73
63 # Raw logs and temporary XDG homes may contain machine-local paths. Keep them74 # Raw logs and temporary XDG homes may contain machine-local paths. Keep them
64 # outside the committed artifact directory and always remove them.75 # outside the committed artifact directory and always remove them.
65-readonly work_root="$(mktemp -d "${TMPDIR:-/tmp}/dumber-first-presentation.XXXXXX")"76+work_root="$(mktemp -d "${TMPDIR:-/tmp}/dumber-first-presentation.XXXXXX")"
77+readonly work_root
66 cleanup_work_root() {78 cleanup_work_root() {
67 [[ -n "${work_root:-}" && -d "$work_root" && ! -L "$work_root" ]] || return79 [[ -n "${work_root:-}" && -d "$work_root" && ! -L "$work_root" ]] || return
68 rm -rf -- "$work_root"80 rm -rf -- "$work_root"
69 }81 }
70 trap cleanup_work_root EXIT82 trap cleanup_work_root EXIT
71 python3 - "$output/metadata.json" "$binary" "$timeout_seconds" "$upstream_module" "$upstream_tag" "$upstream_revision" <<'PY'83 python3 - "$output/metadata.json" "$binary" "$timeout_seconds" "$upstream_module" "$upstream_tag" "$upstream_revision" <<'PY'
72-import hashlib, json, os, subprocess, sys84+import hashlib, json, os, platform, subprocess, sys
73 path, binary, timeout, upstream_module, upstream_tag, upstream_revision = sys.argv[1:]85 path, binary, timeout, upstream_module, upstream_tag, upstream_revision = sys.argv[1:]
74 with open(binary, "rb") as candidate:86 with open(binary, "rb") as candidate:
75 binary_sha256 = hashlib.file_digest(candidate, "sha256").hexdigest()87 binary_sha256 = hashlib.file_digest(candidate, "sha256").hexdigest()
88+
89+# These deliberately coarse labels support comparison without exposing a host,
90+# device name, driver version, path, or environment dump.
91+architecture = {"x86_64": "amd64", "amd64": "amd64", "aarch64": "arm64", "arm64": "arm64"}.get(platform.machine().lower(), "other")
92+os_label = {"linux": "linux", "darwin": "darwin", "windows": "windows"}.get(platform.system().lower(), "other")
93+display_protocol = "wayland" if os.environ.get("WAYLAND_DISPLAY") else "x11"
94+gpu_profile = os.environ.get("DUMBER_MACHINE_GPU_PROFILE", "generic-gpu")
95+allowed_gpu_profiles = {"generic-gpu", "integrated-gpu", "discrete-gpu", "hybrid-gpu", "virtual-gpu", "unknown-gpu"}
96+if gpu_profile not in allowed_gpu_profiles:
97+ raise SystemExit("DUMBER_MACHINE_GPU_PROFILE must be a non-identifying profile label")
98+
76 json.dump({99 json.dump({
77 "runs": 5,100 "runs": 5,
78- "runtime": {"label": "cef", "version": os.environ.get("DUMBER_CEF_RUNTIME_VERSION", "147")},101+ "runtime": {"label": "cef", "version": "147"},
79 "binary": {"label": "dumber", "sha256": binary_sha256},102 "binary": {"label": "dumber", "sha256": binary_sha256},
80 "timeout_seconds": int(timeout),103 "timeout_seconds": int(timeout),
81 "measured_source_revision": subprocess.check_output(["git", "-c", f"safe.directory={os.getcwd()}", "rev-parse", "HEAD"], text=True).strip(),104 "measured_source_revision": subprocess.check_output(["git", "-c", f"safe.directory={os.getcwd()}", "rev-parse", "HEAD"], text=True).strip(),
82 "upstream": {"module": upstream_module, "tag": upstream_tag, "revision": upstream_revision},105 "upstream": {"module": upstream_module, "tag": upstream_tag, "revision": upstream_revision},
83- "fixed_environment": {"DUMBER_RENDER_STACK": "vulkan-dmabuf", "PUREGO_CEF2GTK_BACKEND": "gdk-dmabuf", "GSK_RENDERER": "vulkan"}106+ "comparison": {"os": os_label, "architecture": architecture, "display_protocol": display_protocol, "machine_gpu_profile": gpu_profile},
107+ "render_configuration": {"backend": "gdk-dmabuf", "buffer_sharing": "dmabuf", "renderer": "vulkan"}
84 }, open(path, "w"), indent=2, sort_keys=True)108 }, open(path, "w"), indent=2, sort_keys=True)
85 PY109 PY
86 110

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close