-
Notifications
You must be signed in to change notification settings - Fork 0
400 lines (369 loc) · 20.5 KB
/
Copy pathbuild.yml
File metadata and controls
400 lines (369 loc) · 20.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
name: Build
# Runs on every PR + every push to develop/main. Validates the binary
# builds on the three platforms that tell us something per commit (see the
# `build` job), the tests pass, and the linter is green. The full 8-target
# cross-compile, signing and tag artifacts live in release.yml (Phase 5).
on:
push:
branches: [develop, main]
pull_request:
branches: [develop, main]
permissions:
contents: read
concurrency:
group: build-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
schema-drift:
timeout-minutes: 10
name: Schema drift check
# Verifies the embedded internal/schema/ingest.v1.json matches
# tracebloc/data-ingestors at the PINNED ref (scripts/.data-ingestors-ref),
# not a floating branch. A green PR that silently diverges from the schema
# jobs-manager enforces is a real correctness hazard — a customer's YAML
# could pass `tracebloc ingest validate` locally but be rejected in-cluster
# (or vice versa). Pinning stops an unrelated upstream commit from redding
# every open CLI PR; adopting upstream is a deliberate SHA bump + re-sync
# (backend#1009).
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: scripts/sync-schema.sh --check
run: ./scripts/sync-schema.sh --check
installer:
timeout-minutes: 10
name: Installer (shell)
# The curl|sh installer is the most privileged code we ship (it places the
# binary on PATH) and had NO automated test until R8. shellcheck it under
# the POSIX sh dialect it actually runs as, parse it with dash, and run the
# functional harness that asserts cosign verification is mandatory / fails
# closed when cosign is absent (RFC-0001 R8, backend#889).
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: shellcheck + dash parse
# NO `apt-get` (cli#534). Both tools are already on `ubuntu-latest`:
# `shellcheck` is preinstalled -- tracebloc/.github's own `quality / shellcheck`
# job, a REQUIRED check in 16 repos, calls `shellcheck --version` with no
# install at all -- and `dash` IS Ubuntu's `/bin/sh`, an essential package.
#
# WHY IT HAD TO GO, and it is not tidiness. This step is the first thing in a
# REQUIRED check, and `apt-get` here had no retry and no time bound of its own,
# so a slow package mirror consumed the whole 10-minute job budget before any
# shell was parsed. Measured on cli#533 -- a workflow-only diff that cannot
# touch installer behaviour -- which failed FOUR consecutive times:
#
# job 96126585157 Installer (shell) failure 10m16s
# 15:34 shellcheck + dash parse <- 10 minutes here, then killed
#
# Nothing after the `apt-get` line ever ran, and the annotation said
# `Installer (shell)` exceeded 10m -- pointing whoever reads it at the
# installer rather than at package fetching.
#
# Removing the dependency beats hardening it: a step that installs nothing
# cannot stall on a mirror, and no retry/timeout wrapper can say that.
#
# THIS PR'S OWN RUN IS THE PROOF. If either tool were absent the step fails
# loudly on the first line, here, before merge -- which is a better check than
# any claim in this comment.
run: |
shellcheck --version | head -2
shellcheck --shell=sh --severity=error scripts/install.sh
shellcheck --shell=bash --severity=error scripts/check-style.sh
shellcheck --shell=bash --severity=error scripts/check-tool-pins.sh
shellcheck --shell=bash --severity=error scripts/tests/tool-pins-verify.sh
bash -n scripts/tests/tool-pins-verify.sh
shellcheck --shell=bash --severity=error scripts/format.sh
shellcheck --shell=bash --severity=error scripts/tests/format-verify.sh
shellcheck --shell=bash --severity=error scripts/cosign-retry.sh
shellcheck --shell=bash --severity=error scripts/tests/cosign-retry-verify.sh
bash -n scripts/cosign-retry.sh
bash -n scripts/tests/cosign-retry-verify.sh
dash -n scripts/install.sh
bash -n scripts/tests/install-verify.sh
shellcheck --shell=bash --severity=error scripts/tests/install-ps1-verify.sh
bash -n scripts/tests/install-ps1-verify.sh
shellcheck --shell=bash --severity=error scripts/check-pin-version.sh
shellcheck --shell=bash --severity=error scripts/tests/pin-version-verify.sh
bash -n scripts/check-pin-version.sh
bash -n scripts/tests/pin-version-verify.sh
shellcheck --shell=bash --severity=warning scripts/publish-guard.sh
shellcheck --shell=bash --severity=warning scripts/publish-mirror.sh
shellcheck --shell=bash --severity=error scripts/tests/publish-guard-verify.sh
shellcheck --shell=bash --severity=error scripts/tests/publish-mirror-verify.sh
shellcheck --shell=bash --severity=error scripts/tests/mirror-publish-workflow-verify.sh
bash -n scripts/tests/mirror-publish-workflow-verify.sh
bash -n scripts/publish-guard.sh
bash -n scripts/publish-mirror.sh
shellcheck --shell=bash --severity=warning scripts/backfill-releases.sh
shellcheck --shell=bash --severity=warning scripts/tests/backfill-releases-verify.sh
bash -n scripts/backfill-releases.sh
bash -n scripts/tests/backfill-releases-verify.sh
# format.sh's own fail-closed properties. Formatters are stubbed, so this is
# hermetic and needs no Go toolchain — which is why it lives in this job
# rather than Lint. It exists because the first cut of format.sh reported
# "clean" on a formatter that never ran (#550 review).
- name: Formatter-gate harness (fail-closed / tracked-files scope)
run: bash scripts/tests/format-verify.sh
# check-tool-pins.sh's own properties: both restatement shapes redden
# (`module@version` in a run step, a literal `version:` on the tool's
# action), an unrelated action's `version:` does not, and missing inputs
# fail closed. Hermetic — the real guard runs against a fixture tree.
- name: Tool-pin guard harness (both shapes redden / fail-closed)
run: bash scripts/tests/tool-pins-verify.sh
- name: Verification harness (mandatory cosign / fail-closed)
run: bash scripts/tests/install-verify.sh
# release.yml's cosign call is bounded, retries only transient sigstore
# failures, and fails closed on a zero exit with no signature
# (backend#2379). cosign is stubbed by a PATH shim, so this is hermetic
# and needs no network — which is why it sits in this job rather than
# Release, where it could only be exercised by cutting a tag.
- name: Cosign retry harness (bounded / classified / fail-closed)
run: bash scripts/tests/cosign-retry-verify.sh
# Same property on Windows (backend#2078). pwsh is preinstalled on the
# ubuntu runner image; the harness FAILS rather than skips if it isn't,
# since "cannot tell" is not evidence that verification is mandatory.
- name: Verification harness — Windows (mandatory cosign / fail-closed)
run: bash scripts/tests/install-ps1-verify.sh
# check-pin-version.sh's fail-closed properties: contract-version DRIFT
# reddens (exit 1) and every "cannot evaluate" reddens (exit 2). Fetches
# are file:// URLs and the pin/branch are injected, so this is hermetic
# and needs no network — the live pin-vs-HEAD comparison is the scheduled
# pin-version-drift.yml, which must never gate a PR (backend#2704 / #1009).
- name: Pin-version watcher harness (drift reddens / fail-closed)
run: bash scripts/tests/pin-version-verify.sh
# The mirror-publish guard (scripts/publish-guard.sh) and publisher
# (scripts/publish-mirror.sh): each guard reddens on the thing it claims
# to catch (a forbidden path, a forbidden string, a missing scanner), an
# empty or unreadable list is "could not tell", and the publisher refuses
# an unset or self-pointing mirror. gitleaks is a PATH shim here, so this
# is hermetic; the workflow installs the real, pinned binary.
- name: Mirror-publish guard harness (refusals named / fail-closed)
run: bash scripts/tests/publish-guard-verify.sh
- name: Mirror-publish publisher harness (target / tree / release)
run: bash scripts/tests/publish-mirror-verify.sh
# The decisions mirror-publish.yml takes in its own step bodies — a
# prerelease keeps the mirror's default branch, the release tag is fetched
# as data only at the expected commit, no checkout takes an untrusted
# ref, a publisher refusal reaches the step log. The step bodies are read
# out of the YAML and executed with `gh` shimmed, so this is hermetic.
- name: Mirror-publish workflow harness (step bodies / shape / mutations)
run: bash scripts/tests/mirror-publish-workflow-verify.sh
# The one-shot historical backfill (scripts/backfill-releases.sh): dry-run
# writes nothing, --apply makes exactly the expected writes and a second
# --apply none, binaries stop at the BINARY_KEEP boundary, a binary that
# disagrees with SHA256SUMS or a forbidden string in a body refuses by
# name, a failed read is could-not-tell. `gh` is a recording fake serving
# fixtures, so this is hermetic. The second step breaks one rule per copy
# of the script and demands the same suite go red — a rule the suite
# cannot see reddens the build.
- name: Release-backfill harness (zero-write dry-run / idempotent / fail-closed)
run: bash scripts/tests/backfill-releases-verify.sh
- name: Release-backfill harness — mutations (every anchored rule is load-bearing)
run: bash scripts/tests/backfill-releases-verify.sh --mutations
test:
timeout-minutes: 15
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
cache: true
- name: go vet
run: go vet ./...
- name: go test
# -race catches data races in the test process. The CLI is
# not concurrent today (Phase 0), but the watch/log-stream
# path in Phase 4 will be — enabling -race from day one keeps
# us from having to retrofit it later.
run: go test -race -cover ./...
- name: Coverage floor (cli, submit, push, cluster must not rot)
# `go test -cover` above prints numbers but asserts nothing. This
# enforces a per-package floor on the load-bearing, historically
# thin-tested packages (the money path + submit orchestration) so a
# test deletion can't silently drop coverage. Floors ratchet UP only —
# see scripts/coverage-floor.sh (backend#1009).
run: ./scripts/coverage-floor.sh
lint:
timeout-minutes: 10
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
cache: true
# golangci-lint-action@v6 reproducibly time-budgeted out at
# ~2 minutes on this module's k8s.io-heavy dep tree (3 runs
# in a row in early 2026 — see #6). Stand-alone tools run
# against the same code in <30 seconds and catch the bugs
# we've actually hit this week (unchecked Fprintf errors
# were errcheck-catchable; gofmt -s drift was a recurring
# nit). golangci.yml runs the non-SSA linters through the
# action separately; staticcheck stays standalone here.
# `make lint`, not four inline `go install tool@version` steps. errcheck,
# ineffassign, misspell and staticcheck (`-checks all,-ST1005` — ST1005
# flags ~58 customer-visible error strings that need a deliberate wording
# review, follow-up to #279) each had a second copy of their version here,
# held in step with the Makefile's *_VERSION vars by a comment asking
# people to remember (#127). The Makefile is now the only declaration and
# scripts/check-tool-pins.sh reddens this job if a pin creeps back in —
# the same move cli#549 made for the formatters below and the govulncheck
# job made for its tool.
- name: errcheck + ineffassign + misspell + staticcheck
run: make lint
# gofmt -s (simplification) + goimports -local (the stdlib / third-party /
# our-own import grouping that .golangci.yml's local-prefixes declares;
# gofmt does not check grouping).
#
# `make fmt-check`, not an inline copy: both formatters now scope to
# `git ls-files '*.go'` instead of `.` (cli#549), and a second inline copy
# of that scope here is how local and CI start disagreeing about which
# files are gated. It also drops the restated goimports pin — the version
# is declared once, by GOIMPORTS_VERSION in the Makefile, which is what
# check-tool-pins.sh now enforces for this tool too.
- name: gofmt -s + goimports -local
run: make fmt-check
# deadcode: BLOCKING reachability scan from the CLI entrypoint (~5s).
# The four legit unreachables — Stringer methods (Status.String,
# JobOutcome.String) reached only via fmt reflection that static
# analysis can't see, plus the di#349 test-only parity harnesses
# (ReadLabelValues, inferColumnType) — are declared with reasons in
# scripts/deadcode-allowlist.txt. Anything else unreachable fails the
# job (#281 flipped this from advisory/continue-on-error). Tool version
# pinned inside the script; DEADCODE_VERSION overrides.
- name: deadcode
run: ./scripts/deadcode-check.sh
- name: File budget (line-count ratchet)
# Per-file line ceilings; they only ratchet DOWN — raising one is a
# deliberate, reviewed edit to scripts/file-budget.sh. Keeps the next
# 1500-line data.go from growing quietly (backend#1106 WS-B).
run: ./scripts/file-budget.sh
- name: Style + terminology guard
# Enforces the terminal style system (STYLE.md): no hardcoded brand colour
# outside the tone engine (internal/ui), no status/traffic-light emoji, and
# "secure environment" not "workspace" in user-facing text. Mechanical
# checks only — role/wording judgement stays with review.
run: bash scripts/check-style.sh
- name: Tool-pin guard (one declaration per pinned tool)
# backend#1972: fails if a workflow restates a tool version the Makefile
# already declares. govulncheck's pin lived in three places, kept in step
# by a comment; this is that comment turned into a check.
run: bash scripts/check-tool-pins.sh
govulncheck:
timeout-minutes: 10
name: govulncheck
# Reachability-scans the module for known vulnerabilities (stdlib +
# deps) on every PR and push. This is a customer-installed binary —
# 6 reachable CVEs shipped in v0.8.0 before this gate existed (#276).
#
# backend#1972: the version is NOT restated here. It lived in three
# places — this job, vulncheck.yml, and GOVULNCHECK_VERSION in the
# Makefile — held in lockstep by a comment asking people to remember.
# All three now resolve to the Makefile declaration, because `make
# vulncheck` is what runs. scripts/check-tool-pins.sh fails the Lint
# job if a literal pin creeps back in.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
cache: true
- name: govulncheck ./...
# `make vulncheck`, not a restated `go install ...@version`: a green
# `make ci` locally and this gate can then never disagree about which
# govulncheck ran. Same reasoning as lint-full/GOLANGCI_LINT_VERSION.
run: make vulncheck
build:
timeout-minutes: 20
name: Build (${{ matrix.os }}/${{ matrix.arch }})
# Three legs, not the eight release targets. This job is advisory (it is not a
# required check) and the other five legs were most of a 920 s run — 55 % of
# every push and PR — for compile-only confirmation of platforms that
# release.yml builds, signs and publishes anyway at every tag. What is kept
# is what tells us something per commit:
#
# linux/amd64 — the only leg whose binary can RUN on the runner, so it
# carries the smoke test below.
# windows/amd64 — the one target with its own code path: x/sys/windows
# and the OS-conditional parts of client-go compile only
# under GOOS=windows, and a Linux-only dev loop would not
# notice a break there until the release.
# darwin/arm64 — the team's own machines. The per-PR artifact is how a
# reviewer tries a PR's binary locally before it merges;
# release download counts say nothing about that use.
#
# The 8-target matrix has ONE home now, release.yml — there is no second copy
# here to keep in lock-step. A cross-compile break on any other target
# surfaces at the next rc tag, where fail-fast: false shows every broken leg
# in one run and `publish` refuses to ship a partial release.
runs-on: ubuntu-latest
strategy:
# Don't fail-fast — surface every leg's verdict in one run.
fail-fast: false
matrix:
include:
- os: linux
arch: amd64
- os: windows
arch: amd64
ext: .exe
- os: darwin
arch: arm64
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
cache: true
- name: Build
env:
GOOS: ${{ matrix.os }}
GOARCH: ${{ matrix.arch }}
CGO_ENABLED: "0"
run: |
mkdir -p dist
go build \
-trimpath \
-ldflags "\
-s -w \
-X main.version=${{ github.ref_name }}-${GITHUB_SHA:0:7} \
-X main.gitSHA=${GITHUB_SHA:0:12} \
-X main.buildDate=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
-o dist/tracebloc-${{ matrix.os }}-${{ matrix.arch }}${{ matrix.ext }} \
./cmd/tracebloc
- name: Smoke test (Linux amd64 only — host arch matches runner)
if: matrix.os == 'linux' && matrix.arch == 'amd64'
# The cross-built windows binary can't run on the ubuntu runner,
# so only the native build gets exercised post-compile. The
# windows leg gets compile-time validation only — which is still
# meaningful because client-go and most k8s deps have
# OS-conditional code paths.
run: |
BIN=./dist/tracebloc-linux-amd64
"$BIN" version
"$BIN" version --output-json | python3 -c "import json,sys; d=json.load(sys.stdin); assert d['version'], 'empty version'"
# ingest validate exercises the embedded schema + validation
# wiring on the real binary (no cluster needed): a valid spec
# must pass, and an invalid one must be rejected with exit 2.
"$BIN" ingest validate testdata/smoke/valid-image-classification.yaml
if "$BIN" ingest validate testdata/smoke/invalid-missing-images.yaml; then
echo "::error::ingest validate accepted an invalid spec (missing 'images')"; exit 1
fi
# Command-tree wiring smoke for the dominant verb.
"$BIN" dataset push --help >/dev/null
- name: Upload binary as artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: tracebloc-${{ matrix.os }}-${{ matrix.arch }}
path: dist/tracebloc-${{ matrix.os }}-${{ matrix.arch }}${{ matrix.ext }}
retention-days: 7
if-no-files-found: error