detached f468672

build(systemviews): treat asset manifest as untracked build artifact (#425)

Stop tracking assets/systemviews/asset-manifest.json: it pins the SHA-256
of the gitignored WASM bundle, so every toolchain-affecting dependency
bump (e.g. x/sys) changed the rebuilt WASM hash and broke the
verify-generated CI gate on Dependabot PRs without any reviewable signal.

The runtime trust model is unchanged: go:embed still ships the manifest
with the bundle, ParseManifest stays fail-closed, and
verify-systemviews-assets still checks embedded pins plus raw/compressed
WASM agreement. make build now runs that verification before compiling,
CI test requires DUMBER_REQUIRE_SYSTEMVIEWS_WASM=1, make clean removes
the manifest, and verify-generated only covers tracked artifacts.
commit
f468672eda5e434046deded9b41eda6cb3092854
author
bnema · · 2026-09-10 15:07 UTC
committer
GitHub · · 2026-09-10 15:07 UTC
parent
635dc69
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
8 files · +18 -33
M .github/workflows/ci.yml +1 -0
@@ -140,6 +140,7 @@ jobs:
140140 env:
141141 GDK_BACKEND: wayland
142142 WAYLAND_DISPLAY: wayland-0
143+ DUMBER_REQUIRE_SYSTEMVIEWS_WASM: 1
143144 run: go test -v ./...
144145 - name: Verify purego-only constraints
145146 run: make verify-purego
@@ -140,6 +140,7 @@ jobs:
140 env:140 env:
141 GDK_BACKEND: wayland141 GDK_BACKEND: wayland
142 WAYLAND_DISPLAY: wayland-0142 WAYLAND_DISPLAY: wayland-0
143+ DUMBER_REQUIRE_SYSTEMVIEWS_WASM: 1
143 run: go test -v ./...144 run: go test -v ./...
144 - name: Verify purego-only constraints145 - name: Verify purego-only constraints
145 run: make verify-purego146 run: make verify-purego
M .gitignore +1 -0
@@ -67,6 +67,7 @@ assets/webui
6767 assets/systemviews/systemviews.wasm
6868 assets/systemviews/systemviews.wasm.br
6969 assets/systemviews/wasm_exec.js
70+assets/systemviews/asset-manifest.json
7071
7172 # Node/TypeScript related
7273 node_modules/
@@ -67,6 +67,7 @@ assets/webui
67 assets/systemviews/systemviews.wasm67 assets/systemviews/systemviews.wasm
68 assets/systemviews/systemviews.wasm.br68 assets/systemviews/systemviews.wasm.br
69 assets/systemviews/wasm_exec.js69 assets/systemviews/wasm_exec.js
70+assets/systemviews/asset-manifest.json
70 71
71 # Node/TypeScript related72 # Node/TypeScript related
72 node_modules/73 node_modules/
M Makefile +6 -6
@@ -61,7 +61,7 @@ help: ## Show this help message
6161 @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
6262
6363 # Build targets
64-build: build-systemviews ## Build the application (pure Go, no CGO)
64+build: build-systemviews verify-systemviews-assets ## Build the application (pure Go, no CGO)
6565 @echo "Building $(BINARY_NAME) $(VERSION) using $(NPROCS) cores..."
6666 @mkdir -p $(DIST_DIR)
6767 GOFLAGS="$(NATIVE_GOFLAGS)" CGO_ENABLED=0 go build -buildvcs=false -p $(NPROCS) $(GCFLAGS) $(LDFLAGS) -o $(DIST_DIR)/$(BINARY_NAME) $(MAIN_PATH)
@@ -77,7 +77,7 @@ build-systemviews: generate-systemviews ## Build the WASM systemviews runtime
7777 @echo "Building systemviews wasm assets..."
7878 @command -v brotli >/dev/null 2>&1 || { echo "Error: brotli is required to build compressed systemviews assets. Install brotli and retry."; exit 1; }
7979 @mkdir -p assets/systemviews
80- @cp "$$(GOTOOLCHAIN="$(SYSTEMVIEWS_GOTOOLCHAIN)" go env GOROOT)/lib/wasm/wasm_exec.js" assets/systemviews/wasm_exec.js
80+ @cp -f "$$(GOTOOLCHAIN="$(SYSTEMVIEWS_GOTOOLCHAIN)" go env GOROOT)/lib/wasm/wasm_exec.js" assets/systemviews/wasm_exec.js
8181 GOFLAGS="$(WASM_GOFLAGS)" GOTOOLCHAIN="$(SYSTEMVIEWS_GOTOOLCHAIN)" GOENV=off GOOS=js GOARCH=wasm go build -trimpath -buildvcs=false -ldflags="-s -w" -o assets/systemviews/systemviews.wasm ./cmd/systemviews
8282 brotli -f -o assets/systemviews/systemviews.wasm.br assets/systemviews/systemviews.wasm
8383 go run ./cmd/systemviews-assets -dir assets/systemviews
@@ -150,10 +150,10 @@ stress-omnibox-callbacks: ## Run placeholder omnibox callback stress harness
150150 verify-purego: ## Ensure callback path stays cgo/export free
151151 bash ./scripts/verify_purego_only.sh
152152
153-verify-generated: ## Verify generated systemviews artifacts are committed
154- @echo "Verifying generated systemviews artifacts..."
153+verify-generated: ## Verify tracked generated systemviews artifacts are committed
154+ @echo "Verifying tracked generated systemviews artifacts..."
155155 @git diff --exit-code -- assets/systemviews internal/ui/systemviews || { \
156- echo "Generated systemviews artifacts are out of date. Run 'make build-systemviews' and commit the result."; \
156+ echo "Tracked generated systemviews artifacts are out of date. Run 'make build-systemviews' and commit the result. (The asset manifest itself is an untracked build artifact and is not compared.)"; \
157157 exit 1; \
158158 }
159159
@@ -204,7 +204,7 @@ clean: ## Clean build artifacts
204204 @echo "Cleaning build artifacts..."
205205 rm -rf $(DIST_DIR)
206206 rm -f $(BINARY_NAME)
207- rm -f assets/systemviews/wasm_exec.js assets/systemviews/systemviews.wasm assets/systemviews/systemviews.wasm.br
207+ rm -f assets/systemviews/wasm_exec.js assets/systemviews/systemviews.wasm assets/systemviews/systemviews.wasm.br assets/systemviews/asset-manifest.json
208208 rm -f coverage.out coverage.html
209209 go clean -cache
210210 go clean -testcache
@@ -61,7 +61,7 @@ help: ## Show this help message
61 @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)61 @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
62 62
63 # Build targets63 # Build targets
64-build: build-systemviews ## Build the application (pure Go, no CGO)64+build: build-systemviews verify-systemviews-assets ## Build the application (pure Go, no CGO)
65 @echo "Building $(BINARY_NAME) $(VERSION) using $(NPROCS) cores..."65 @echo "Building $(BINARY_NAME) $(VERSION) using $(NPROCS) cores..."
66 @mkdir -p $(DIST_DIR)66 @mkdir -p $(DIST_DIR)
67 GOFLAGS="$(NATIVE_GOFLAGS)" CGO_ENABLED=0 go build -buildvcs=false -p $(NPROCS) $(GCFLAGS) $(LDFLAGS) -o $(DIST_DIR)/$(BINARY_NAME) $(MAIN_PATH)67 GOFLAGS="$(NATIVE_GOFLAGS)" CGO_ENABLED=0 go build -buildvcs=false -p $(NPROCS) $(GCFLAGS) $(LDFLAGS) -o $(DIST_DIR)/$(BINARY_NAME) $(MAIN_PATH)
@@ -77,7 +77,7 @@ build-systemviews: generate-systemviews ## Build the WASM systemviews runtime
77 @echo "Building systemviews wasm assets..."77 @echo "Building systemviews wasm assets..."
78 @command -v brotli >/dev/null 2>&1 || { echo "Error: brotli is required to build compressed systemviews assets. Install brotli and retry."; exit 1; }78 @command -v brotli >/dev/null 2>&1 || { echo "Error: brotli is required to build compressed systemviews assets. Install brotli and retry."; exit 1; }
79 @mkdir -p assets/systemviews79 @mkdir -p assets/systemviews
80- @cp "$$(GOTOOLCHAIN="$(SYSTEMVIEWS_GOTOOLCHAIN)" go env GOROOT)/lib/wasm/wasm_exec.js" assets/systemviews/wasm_exec.js80+ @cp -f "$$(GOTOOLCHAIN="$(SYSTEMVIEWS_GOTOOLCHAIN)" go env GOROOT)/lib/wasm/wasm_exec.js" assets/systemviews/wasm_exec.js
81 GOFLAGS="$(WASM_GOFLAGS)" GOTOOLCHAIN="$(SYSTEMVIEWS_GOTOOLCHAIN)" GOENV=off GOOS=js GOARCH=wasm go build -trimpath -buildvcs=false -ldflags="-s -w" -o assets/systemviews/systemviews.wasm ./cmd/systemviews81 GOFLAGS="$(WASM_GOFLAGS)" GOTOOLCHAIN="$(SYSTEMVIEWS_GOTOOLCHAIN)" GOENV=off GOOS=js GOARCH=wasm go build -trimpath -buildvcs=false -ldflags="-s -w" -o assets/systemviews/systemviews.wasm ./cmd/systemviews
82 brotli -f -o assets/systemviews/systemviews.wasm.br assets/systemviews/systemviews.wasm82 brotli -f -o assets/systemviews/systemviews.wasm.br assets/systemviews/systemviews.wasm
83 go run ./cmd/systemviews-assets -dir assets/systemviews83 go run ./cmd/systemviews-assets -dir assets/systemviews
@@ -150,10 +150,10 @@ stress-omnibox-callbacks: ## Run placeholder omnibox callback stress harness
150 verify-purego: ## Ensure callback path stays cgo/export free150 verify-purego: ## Ensure callback path stays cgo/export free
151 bash ./scripts/verify_purego_only.sh151 bash ./scripts/verify_purego_only.sh
152 152
153-verify-generated: ## Verify generated systemviews artifacts are committed153+verify-generated: ## Verify tracked generated systemviews artifacts are committed
154- @echo "Verifying generated systemviews artifacts..."154+ @echo "Verifying tracked generated systemviews artifacts..."
155 @git diff --exit-code -- assets/systemviews internal/ui/systemviews || { \155 @git diff --exit-code -- assets/systemviews internal/ui/systemviews || { \
156- echo "Generated systemviews artifacts are out of date. Run 'make build-systemviews' and commit the result."; \156+ echo "Tracked generated systemviews artifacts are out of date. Run 'make build-systemviews' and commit the result. (The asset manifest itself is an untracked build artifact and is not compared.)"; \
157 exit 1; \157 exit 1; \
158 }158 }
159 159
@@ -204,7 +204,7 @@ clean: ## Clean build artifacts
204 @echo "Cleaning build artifacts..."204 @echo "Cleaning build artifacts..."
205 rm -rf $(DIST_DIR)205 rm -rf $(DIST_DIR)
206 rm -f $(BINARY_NAME)206 rm -f $(BINARY_NAME)
207- rm -f assets/systemviews/wasm_exec.js assets/systemviews/systemviews.wasm assets/systemviews/systemviews.wasm.br207+ rm -f assets/systemviews/wasm_exec.js assets/systemviews/systemviews.wasm assets/systemviews/systemviews.wasm.br assets/systemviews/asset-manifest.json
208 rm -f coverage.out coverage.html208 rm -f coverage.out coverage.html
209 go clean -cache209 go clean -cache
210 go clean -testcache210 go clean -testcache
M README.md +1 -1
@@ -233,7 +233,7 @@ make build
233233 | `make test` | Run tests |
234234 | `make lint` | Run the pinned golangci-lint version |
235235 | `make staticcheck` | Run Staticcheck with the pinned tool version |
236-| `make verify-generated` | Verify generated systemviews artifacts are committed |
236+| `make verify-generated` | Verify tracked generated systemviews artifacts are committed |
237237 | `make flatpak-build` | Build Flatpak bundle |
238238
239239 Development tool versions are pinned in `Makefile` (`GOLANGCI_LINT_VERSION`, `STATICCHECK_VERSION`). Bump those values intentionally when refreshing lint/static analysis tooling.
@@ -233,7 +233,7 @@ make build
233 | `make test` | Run tests |233 | `make test` | Run tests |
234 | `make lint` | Run the pinned golangci-lint version |234 | `make lint` | Run the pinned golangci-lint version |
235 | `make staticcheck` | Run Staticcheck with the pinned tool version |235 | `make staticcheck` | Run Staticcheck with the pinned tool version |
236-| `make verify-generated` | Verify generated systemviews artifacts are committed |236+| `make verify-generated` | Verify tracked generated systemviews artifacts are committed |
237 | `make flatpak-build` | Build Flatpak bundle |237 | `make flatpak-build` | Build Flatpak bundle |
238 238
239 Development tool versions are pinned in `Makefile` (`GOLANGCI_LINT_VERSION`, `STATICCHECK_VERSION`). Bump those values intentionally when refreshing lint/static analysis tooling.239 Development tool versions are pinned in `Makefile` (`GOLANGCI_LINT_VERSION`, `STATICCHECK_VERSION`). Bump those values intentionally when refreshing lint/static analysis tooling.
M assets/manifest.go +3 -3
@@ -4,9 +4,9 @@ package assets
44 //
55 // The manifest pins the SHA-256 and size of every uncompressed immutable
66 // asset the CEF scheme handler may serve with long-lived cache headers.
7-// It is generated by cmd/systemviews-assets after the WASM build and
8-// committed; verification compares it against the actually embedded
9-// artifacts, never against Git state. Encoding is canonical JSON with
7+// It is generated by cmd/systemviews-assets after the WASM build as an
8+// untracked build artifact; verification compares it against the actually
9+// embedded artifacts, never against Git state. Encoding is canonical JSON with
1010 // sorted keys and no timestamps or paths, so identical inputs always
1111 // produce identical bytes.
1212
@@ -4,9 +4,9 @@ package assets
4 //4 //
5 // The manifest pins the SHA-256 and size of every uncompressed immutable5 // The manifest pins the SHA-256 and size of every uncompressed immutable
6 // asset the CEF scheme handler may serve with long-lived cache headers.6 // asset the CEF scheme handler may serve with long-lived cache headers.
7-// It is generated by cmd/systemviews-assets after the WASM build and7+// It is generated by cmd/systemviews-assets after the WASM build as an
8-// committed; verification compares it against the actually embedded8+// untracked build artifact; verification compares it against the actually
9-// artifacts, never against Git state. Encoding is canonical JSON with9+// embedded artifacts, never against Git state. Encoding is canonical JSON with
10 // sorted keys and no timestamps or paths, so identical inputs always10 // sorted keys and no timestamps or paths, so identical inputs always
11 // produce identical bytes.11 // produce identical bytes.
12 12
D assets/systemviews/asset-manifest.json +0 -17
@@ -1,17 +0,0 @@
1-{
2- "version": 1,
3- "files": {
4- "systemviews.css": {
5- "sha256": "dcb45554912b04e850ec66d6a8c71050a7fab03124bf7d96c3e97e043345a847",
6- "size": 19551
7- },
8- "systemviews.wasm": {
9- "sha256": "77211e1f943a71c1eca160aad9a48bc57e8eee33c6e8fbd3b300b302afaf5c6f",
10- "size": 8168388
11- },
12- "wasm_exec.js": {
13- "sha256": "0c949f4996f9a89698e4b5c586de32249c3b69b7baadb64d220073cc04acba14",
14- "size": 16992
15- }
16- }
17-}
@@ -1,17 +0,0 @@
1-{
2- "version": 1,
3- "files": {
4- "systemviews.css": {
5- "sha256": "dcb45554912b04e850ec66d6a8c71050a7fab03124bf7d96c3e97e043345a847",
6- "size": 19551
7- },
8- "systemviews.wasm": {
9- "sha256": "77211e1f943a71c1eca160aad9a48bc57e8eee33c6e8fbd3b300b302afaf5c6f",
10- "size": 8168388
11- },
12- "wasm_exec.js": {
13- "sha256": "0c949f4996f9a89698e4b5c586de32249c3b69b7baadb64d220073cc04acba14",
14- "size": 16992
15- }
16- }
17-}
M cmd/systemviews-assets/main.go +4 -4
@@ -6,7 +6,7 @@
66 // bounded read, and writes asset-manifest.json. It fails loudly on stale
77 // or missing inputs instead of writing a partial manifest.
88 //
9-// Check mode (-check) validates the committed manifest against the
9+// Check mode (-check) validates the generated bundle manifest against the
1010 // artifacts on disk and exits nonzero on any mismatch, for CI and
1111 // release verification.
1212 package main
@@ -31,12 +31,12 @@ import (
3131 // serving bound so a manifest can never bless what serving would reject.
3232 const maxManifestWASMBytes = 64 * 1024 * 1024
3333
34-// manifestFilePerm matches committed repo asset files.
34+// manifestFilePerm matches repo asset files.
3535 const manifestFilePerm = 0o644
3636
3737 func main() {
3838 dir := flag.String("dir", "assets/systemviews", "directory holding the built systemview assets")
39- check := flag.Bool("check", false, "verify the committed manifest against disk artifacts instead of regenerating")
39+ check := flag.Bool("check", false, "verify the generated manifest against disk artifacts instead of regenerating")
4040 checkIfPresent := flag.Bool("check-if-present", false, "like -check, but pass with a notice when no built WASM exists (for quick builds)")
4141 flag.Parse()
4242
@@ -78,7 +78,7 @@ func run(dir string, check, checkIfPresent bool) error {
7878 return nil
7979 }
8080
81-// checkTree validates the committed manifest against disk artifacts:
81+// checkTree validates the generated manifest against disk artifacts:
8282 // pins, raw bytes, and raw/compressed WASM agreement. Generation checks
8383 // agreement at write time, but only the check path guards a later
8484 // compressed-only swap, which serving would treat as authoritative.
@@ -6,7 +6,7 @@
6 // bounded read, and writes asset-manifest.json. It fails loudly on stale6 // bounded read, and writes asset-manifest.json. It fails loudly on stale
7 // or missing inputs instead of writing a partial manifest.7 // or missing inputs instead of writing a partial manifest.
8 //8 //
9-// Check mode (-check) validates the committed manifest against the9+// Check mode (-check) validates the generated bundle manifest against the
10 // artifacts on disk and exits nonzero on any mismatch, for CI and10 // artifacts on disk and exits nonzero on any mismatch, for CI and
11 // release verification.11 // release verification.
12 package main12 package main
@@ -31,12 +31,12 @@ import (
31 // serving bound so a manifest can never bless what serving would reject.31 // serving bound so a manifest can never bless what serving would reject.
32 const maxManifestWASMBytes = 64 * 1024 * 102432 const maxManifestWASMBytes = 64 * 1024 * 1024
33 33
34-// manifestFilePerm matches committed repo asset files.34+// manifestFilePerm matches repo asset files.
35 const manifestFilePerm = 0o64435 const manifestFilePerm = 0o644
36 36
37 func main() {37 func main() {
38 dir := flag.String("dir", "assets/systemviews", "directory holding the built systemview assets")38 dir := flag.String("dir", "assets/systemviews", "directory holding the built systemview assets")
39- check := flag.Bool("check", false, "verify the committed manifest against disk artifacts instead of regenerating")39+ check := flag.Bool("check", false, "verify the generated manifest against disk artifacts instead of regenerating")
40 checkIfPresent := flag.Bool("check-if-present", false, "like -check, but pass with a notice when no built WASM exists (for quick builds)")40 checkIfPresent := flag.Bool("check-if-present", false, "like -check, but pass with a notice when no built WASM exists (for quick builds)")
41 flag.Parse()41 flag.Parse()
42 42
@@ -78,7 +78,7 @@ func run(dir string, check, checkIfPresent bool) error {
78 return nil78 return nil
79 }79 }
80 80
81-// checkTree validates the committed manifest against disk artifacts:81+// checkTree validates the generated manifest against disk artifacts:
82 // pins, raw bytes, and raw/compressed WASM agreement. Generation checks82 // pins, raw bytes, and raw/compressed WASM agreement. Generation checks
83 // agreement at write time, but only the check path guards a later83 // agreement at write time, but only the check path guards a later
84 // compressed-only swap, which serving would treat as authoritative.84 // compressed-only swap, which serving would treat as authoritative.
M scripts/test_systemview_startup.py +2 -2
@@ -3,7 +3,7 @@
33
44 Static gates always run headless: the shell must fetch the WASM exactly
55 once (clone-based fallback), carry the readiness contract markers, and
6-the committed manifest must match disk artifacts.
6+the generated (untracked) manifest must match disk artifacts.
77
88 The live launch smoke starts the candidate binary with an isolated
99 profile, watches for panics/fatal crashes, then terminates it gracefully.
@@ -97,7 +97,7 @@ def check_shell_readiness_contract():
9797
9898
9999 def check_manifest_on_disk():
100- """P3.1: committed manifest parses and matches generator schema."""
100+ """P3.1: generated manifest parses and matches generator schema."""
101101 try:
102102 raw = MANIFEST_PATH.read_bytes()
103103 except OSError as exc:
@@ -3,7 +3,7 @@
3 3
4 Static gates always run headless: the shell must fetch the WASM exactly4 Static gates always run headless: the shell must fetch the WASM exactly
5 once (clone-based fallback), carry the readiness contract markers, and5 once (clone-based fallback), carry the readiness contract markers, and
6-the committed manifest must match disk artifacts.6+the generated (untracked) manifest must match disk artifacts.
7 7
8 The live launch smoke starts the candidate binary with an isolated8 The live launch smoke starts the candidate binary with an isolated
9 profile, watches for panics/fatal crashes, then terminates it gracefully.9 profile, watches for panics/fatal crashes, then terminates it gracefully.
@@ -97,7 +97,7 @@ def check_shell_readiness_contract():
97 97
98 98
99 def check_manifest_on_disk():99 def check_manifest_on_disk():
100- """P3.1: committed manifest parses and matches generator schema."""100+ """P3.1: generated manifest parses and matches generator schema."""
101 try:101 try:
102 raw = MANIFEST_PATH.read_bytes()102 raw = MANIFEST_PATH.read_bytes()
103 except OSError as exc:103 except OSError as exc:

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