@@ -9,6 +9,279 @@ The format is based on the regulated environment requirements:
99
1010---
1111
12+ ## [ 2026-04-22 21:15] - Drop AUTO_VEX_PRESENCE gate; Security team is the verifier
13+
14+ ** Author:** Erick Bourgeois
15+
16+ ### Changed
17+ - ` .github/workflows/build.yaml ` :
18+ - ` build-vex ` job: removed the ` vars.AUTO_VEX_PRESENCE == '1' `
19+ conditional on the auto-presence download step. The artifact is
20+ now downloaded unconditionally, and the merge command includes
21+ ` auto/vex.auto-presence.json ` on every build. Added an explicit
22+ ` test -f auto/vex.auto-presence.json || exit 1 ` guard so a
23+ missing artifact fails the job (rather than silently producing
24+ hand-authored-only VEX).
25+ - Comment headers on both ` auto-vex-presence ` and ` build-vex `
26+ rewritten to describe the new trust model: CI emits aggressively,
27+ Security team verifies downstream and counter-signs.
28+ - ` .vex/README.md ` : removed the "parallel-run gate / review-only"
29+ language. Auto-presence statements now merge into the signed VEX on
30+ every build.
31+ - ` docs/src/security/vex.md ` :
32+ - CI flow step 3: removed the ` AUTO_VEX_PRESENCE=1 ` gate clause.
33+ - Automation-split section: the "Automated" bullet no longer
34+ mentions a feature flag.
35+ - New top-level "Trust model" section describing the two-signature
36+ workflow: CI-side Cosign attestation (keyless OIDC via GitHub)
37+ first, Security-team Cosign attestation (their own OIDC identity)
38+ second. Each sits in the Sigstore transparency log; downstream
39+ consumers can require both via twin
40+ ` --certificate-identity-regexp ` invocations.
41+ - ` ~/dev/roadmaps/5spot-automated-vex-generation.md ` :
42+ - Top-of-file status line updated: Phase 1 ✅, Phase 2 ✅ (always-on),
43+ Phase 3 ⏳ scoped.
44+ - New "Trust model (load-bearing)" section at the top — replaces
45+ the implicit assumption in the earlier draft that our side would
46+ be the verification gate.
47+ - Phase 2 "Status" line updated to reflect the gate removal.
48+ - Phase 3 tradeoff section simplified: "two independent call-graph
49+ analyses must agree" and "multi-release parallel-run" removed;
50+ the Security team's re-verification is the second check.
51+ - Rollout section rewritten: no per-phase feature flags; each
52+ phase activates on merge.
53+
54+ ### Why
55+ Policy flip requested by the user. The original Phase 2 rollout plan
56+ had an ` AUTO_VEX_PRESENCE ` repo variable that gated whether
57+ auto-generated statements flowed into the signed VEX document; the
58+ intent was a parallel-run validation window where maintainers could
59+ diff the auto-set against ` .vex/ ` before authorizing it. The revised
60+ trust model moves that validation downstream to the Security team,
61+ which re-derives each machine-authored claim from the signed evidence
62+ (SBOM, call-graph attestations, SLSA provenance) and counter-signs if
63+ they agree. That makes our side's gate redundant — if we get it
64+ wrong, the VEX ships with only one attestation instead of two, which
65+ is a discoverable condition for any downstream consumer running a
66+ two-signature verify gate.
67+
68+ The trust model extends to the not-yet-implemented Phase 3
69+ (reachability-based auto-VEX): it also ships unconditionally when
70+ built, with call-graph attestations providing the evidence for
71+ Security to verify.
72+
73+ ### Verification
74+ - ` python3 -c "import yaml; yaml.safe_load(open('.github/workflows/build.yaml'))" `
75+ → valid YAML.
76+ - ` grep "AUTO_VEX_PRESENCE" .github/workflows/build.yaml ` → no
77+ residual references.
78+ - No Rust code touched; ` cargo ` checks unchanged from the previous
79+ entry.
80+
81+ ### Impact
82+ - [ ] Breaking change
83+ - [ ] Requires cluster rollout
84+ - [x] Config change only — specifically, removes a config knob that
85+ existed for ~ 1 hour and was never documented as user-facing.
86+ - [x] Documentation only
87+
88+ ---
89+
90+ ## [ 2026-04-22 20:30] - Presence-based auto-VEX (roadmap Phase 2)
91+
92+ ** Author:** Erick Bourgeois
93+
94+ ### Added
95+ - ` src/auto_vex_presence.rs ` (library module, ~ 180 LOC) + ` src/auto_vex_presence_tests.rs `
96+ (20 unit tests, 100% positive/negative/exception coverage per project rule):
97+ - ` compute_presence_vex ` — pure function that diffs Grype findings against
98+ SBOMs and the already-triaged CVE set, emitting
99+ ` not_affected + component_not_present ` statements for CVEs whose purl
100+ is absent from every SBOM.
101+ - ` build_document ` — wraps statements in the OpenVEX envelope.
102+ - ` load_triaged_from_vex_dir ` — reads hand-authored ` .vex/*.json ` and
103+ collects the set of ` vulnerability.name ` values (permissive on
104+ missing dir, strict on malformed JSON).
105+ - Typed deserializers for Grype JSON and CycloneDX SBOM subsets —
106+ tolerant of unknown upstream fields.
107+ - Output sorted by CVE id for deterministic, diffable artifacts.
108+ - ` src/bin/auto_vex_presence.rs ` — thin clap-driven CLI wrapping the
109+ library. Reads ` --grype-json ` , one or more ` --sbom ` files, a
110+ ` --vex-dir ` , a ` --product-purl ` , ` --id ` , optional ` --author ` and
111+ ` --timestamp ` , writes an OpenVEX JSON to ` --output ` or stdout.
112+ - ` Cargo.toml ` : ` [[bin]] auto-vex-presence ` entry.
113+ - ` Makefile ` : ` vex-auto-presence ` target (defaults ` GRYPE_JSON=grype.json `
114+ and ` SBOM_FILES=target/release/*.cdx.json docker-sbom-*.json ` ; both
115+ overridable). Added to ` .PHONY ` .
116+ - ` .github/workflows/build.yaml ` : two new jobs gated on
117+ ` github.event_name != 'pull_request' ` :
118+ - ` grype-triage ` — runs Grype against each image variant without VEX
119+ suppression, uploads per-variant JSON (pinned GRYPE_VERSION 0.87.0
120+ matching the existing ` grype ` job).
121+ - ` auto-vex-presence ` — builds the new bin, downloads both triage
122+ scans + Docker SBOMs, runs the bin once per variant, merges the
123+ per-variant outputs via ` vexctl merge ` , uploads
124+ ` vex-auto-presence ` artifact.
125+
126+ ### Changed
127+ - ` .github/workflows/build.yaml ` :
128+ - Docker SBOM generation (` Generate Docker SBOM for ${{ matrix.variant.name }} ` ):
129+ gate loosened from ` if: github.event_name == 'release' ` to
130+ ` if: github.event_name != 'pull_request' ` so push-to-main also
131+ produces SBOMs for auto-vex-presence to consume.
132+ - ` build-vex ` job: ` needs ` extended to include ` auto-vex-presence ` .
133+ Added a feature-flagged download step (` if: vars.AUTO_VEX_PRESENCE == '1' ` )
134+ and conditional merge inclusion. When the repo variable is
135+ ` AUTO_VEX_PRESENCE=1 ` , ` vex.auto-presence.json ` is merged alongside
136+ ` .vex/*.json ` ; otherwise hand-authored only. Artifact is produced
137+ regardless — only the merge is gated.
138+ - ` src/lib.rs ` : re-export ` pub mod auto_vex_presence ` .
139+ - ` docs/src/security/vex.md ` : CI flow section renumbered to 6 steps;
140+ added step 2 for auto-presence. Rewrote "Why we did not auto-generate
141+ statements" section as "What we automate, and what stays human" — the
142+ policy now distinguishes ` component_not_present ` (mechanical,
143+ automatable) from every other triage decision (human-authored).
144+ - ` .vex/README.md ` : added "Automated statements (roadmap Phase 2)"
145+ section explaining the review-artifact vs flag-gated-merge split.
146+
147+ ### Why
148+ Phase 2 of the automated VEX generation roadmap
149+ (` ~/dev/roadmaps/5spot-automated-vex-generation.md ` ). Most of the
150+ hand-authored statements under ` .vex/ ` exist because Grype flags
151+ CVEs on libc / glibc / zlib code paths that the Rust binary never
152+ reaches — i.e. the vulnerable component is present in the image
153+ filesystem but never invoked. A subset of those (plus most
154+ base-image CVEs going forward) can be suppressed mechanically by
155+ observing that the flagged package's purl is not in the SBOM at
156+ all: the SBOM is authoritative for what's in the product, so
157+ "component_not_present" is the one OpenVEX justification with a
158+ purely verifiable definition. Automating that specific case shrinks
159+ the ` .vex/ ` maintenance queue without compromising the trust model
160+ for any other justification.
161+
162+ The implementation is strictly SBOM-driven — no reachability analysis,
163+ no source-code inspection. Reachability is Phase 3.
164+
165+ ### Rollout / feature-flag
166+ Per the roadmap rollout plan, the auto-presence artifact is
167+ produced and uploaded on every push + release, but it is ** only
168+ merged into the signed VEX document when ` vars.AUTO_VEX_PRESENCE=1 `
169+ is set** at the repository or organization level. This gives
170+ maintainers at least one release of parallel-run validation: the
171+ artifact is visible for review, but consumers see only the
172+ hand-authored VEX until the flag is flipped. This matches the
173+ roadmap's explicit guidance ("Run in parallel with hand-authored VEX
174+ for one release; diff the auto-set against .vex/ and confirm no
175+ surprises before flipping it on").
176+
177+ ### Verification
178+ - ` cargo fmt --all -- --check ` : clean.
179+ - ` cargo clippy --all-targets --all-features -- -D warnings ` : clean.
180+ - ` cargo test --lib ` : 349 tests pass (329 pre-existing + 20 new for
181+ ` auto_vex_presence ` ).
182+ - Smoke test: ran the bin against a synthetic Grype JSON (3 matches),
183+ synthetic SBOM (covering 1 of the 3 purls), and the current ` .vex/ `
184+ directory (covering 1 of the 3 CVEs). The bin emitted exactly the
185+ one statement for the remaining CVE whose purl was absent, and
186+ ` vexctl merge ` of the auto-presence output + ` .vex/*.json `
187+ produced a 16-statement document with the auto statement correctly
188+ included.
189+
190+ ### Impact
191+ - [ ] Breaking change
192+ - [x] Requires cluster rollout (no — CI only)
193+ - [ ] Config change only
194+ - [x] Documentation only
195+
196+ * (Rollout note: when maintainers want to flip ` AUTO_VEX_PRESENCE=1 ` ,
197+ that's a repo-variable change in GitHub settings, no code deploy.)*
198+
199+ ---
200+
201+ ## [ 2026-04-22 19:45] - Replace Python VEX tooling with vexctl (roadmap Phase 1)
202+
203+ ** Author:** Erick Bourgeois
204+
205+ ### Removed
206+ - ` tools/ ` — entire directory deleted. Removed ` assemble_openvex.py ` ,
207+ ` validate_vex.py ` , ` validate-vex.sh ` , ` tests/assemble-openvex-tests.sh ` ,
208+ ` tests/validate-vex-tests.sh ` , and all 18 fixture directories under
209+ ` tests/fixtures/ ` . ~ 400 LOC of bespoke Python + shell tests replaced
210+ by upstream ` vexctl ` invocations.
211+
212+ ### Changed
213+ - ` .vex/*.toml ` → ` .vex/*.json ` (15 files): migrated every statement
214+ from the bespoke TOML dialect to the native OpenVEX v0.2.0 JSON
215+ shape. Each file is now a single-statement OpenVEX document that
216+ ` vexctl merge ` can consume directly; no translation layer.
217+ - ` Makefile ` : added ` VEXCTL_VERSION ?= 0.4.1 ` , ` vexctl-install ` target
218+ (brew on macOS, pinned GitHub release tarball with checksum
219+ verification on Linux; errors on other OSes), ` vex-validate `
220+ (parses every ` .vex/*.json ` via ` vexctl merge ` — successful parse
221+ is the validation), and ` vex-assemble ` (prints the merged document
222+ for local preview). ` .PHONY ` list updated.
223+ - ` .github/workflows/build.yaml ` :
224+ - ` validate-vex ` job: dropped Python setup + custom validator calls;
225+ now runs ` make vexctl-install ` then ` make vex-validate ` .
226+ - ` build-vex ` job: dropped Python setup + ` assemble_openvex.py ` ;
227+ now runs `vexctl merge --id <per-event-id > --author <actor >
228+ .vex/* .json` directly. Per-event ` @id` logic (release / push /
229+ PR) preserved verbatim. Cosign attestation, artifact upload,
230+ and ` attest-build-provenance ` steps unchanged.
231+ - PR ` paths: ` filter: removed ` tools/** ` (directory no longer
232+ exists); ` .vex/** ` retained.
233+ - ` .vex/README.md ` : rewritten to document native OpenVEX JSON
234+ authoring and ` make vex-validate ` / ` make vex-assemble ` instead of
235+ the old TOML schema and shell validator.
236+ - ` docs/src/security/vex.md ` : CI flow section updated — step 1
237+ ("validate") and step 2 ("assemble") now describe ` vexctl merge `
238+ behaviour; dropped the obsolete "cross-check with ` vexctl validate ` "
239+ step (vexctl has no ` validate ` subcommand). Maintainer-authoring
240+ section rewritten with the JSON shape in place of the TOML shape.
241+ - ` .claude/SKILL.md ` : release checklist item for VEX updated from the
242+ old three-script check to ` make vex-validate ` .
243+
244+ ### Why
245+ Phase 1 of the automated VEX generation roadmap
246+ (` ~/dev/roadmaps/5spot-automated-vex-generation.md ` ). The custom
247+ Python toolchain duplicated functionality that ` vexctl ` — the official
248+ OpenVEX CLI — provides upstream. Maintaining a bespoke TOML dialect,
249+ its validator, its assembler, and two fixture-based test suites
250+ added ~ 400 LOC of surface area with no offsetting benefit once we
251+ accept OpenVEX JSON as the authoring format. Replacing the whole
252+ stack with ` vexctl merge ` (which doubles as the validator via
253+ successful parse) removes the Python runtime from CI entirely,
254+ eliminates a class of drift between our validator and the upstream
255+ spec, and prepares the pipeline for Phases 2–3 (automated VEX
256+ generation from SBOM and call-graph reachability).
257+
258+ The TOML-over-JSON ergonomics hit is real but small: 15 files in
259+ ` .vex/ ` , each ~ 15 lines of JSON, with no inline comments. In
260+ exchange, future maintainers work with the same format upstream
261+ tooling speaks and any OpenVEX-aware tool can consume the source
262+ directory directly.
263+
264+ ### Verification
265+ - ` make vex-validate ` parses all 15 ` .vex/*.json ` successfully.
266+ - ` vexctl merge --id <x> --author <y> .vex/*.json ` produces a
267+ 15-statement document structurally identical to what the Python
268+ assembler would have produced (confirmed via key-by-key diff of
269+ ` @context ` , ` @id ` , ` author ` , ` version ` , and every statement's
270+ ` products ` , ` status ` , ` justification ` , ` impact_statement ` ,
271+ ` action_statement ` , ` timestamp ` ).
272+
273+ ### Impact
274+ - [ ] Breaking change
275+ - [ ] Requires cluster rollout
276+ - [ ] Config change only
277+ - [x] Documentation only
278+
279+ * (Not really "documentation only" — code deletion is substantive —
280+ but no runtime, CRD, or API-surface change. Closest pre-existing
281+ box.)*
282+
283+ ---
284+
12285## [ 2026-04-21 15:00] - Model emergency-reclaim in CALM — ADD retrofit
13286
14287** Author:** Erick Bourgeois
0 commit comments