fix(config): require trunk_branch so a manifest cannot silently generate a dead pipeline - #629
Conversation
…ate a dead pipeline Omitting trunk_branch passed lint with no diagnostics and generated an orchestrate workflow whose push trigger read "branches: []", an allow-list matching no branch. GitHub accepts that workflow and reports it green while it never fires on a trunk push. The schema has always listed trunk_branch as required, so lint and the schema disagreed and the docs claimed it was both required and defaulted to main. 27 of the 31 generatable manifest examples in the documentation emitted a dead workflow. Enforce it in lint to match the schema, guard the generator against emitting any empty trigger allow-list, fix every documented example, and validate the docs examples against the schema with the test that already guards the README. No default is inferred: guessing main for a repository whose trunk is named otherwise would rebuild the same dead workflow silently. Signed-off-by: Joshua Temple <joshua.temple@stablekernel.com>
8f13400 to
ad4c60b
Compare
|
Pushed Chose to make the guard true to its claim rather than narrow the wording. Verifying the finding first turned up one more instance than reported:
Red-first: the cases are unreachable via config today (every block-style site is length-guarded), which makes the function itself the unit under test. No false positives: 3437 pass (root, and under CHANGELOG reworded to what the check does rather than an aspirational fragment list. Gates: build, 3437 tests, race, |
… claims The guard's comment claimed it caught any present-but-empty trigger allow-list from any field or code path, but it scanned four literal fragments with a first-occurrence-only match. Block-style paths:, tags:, and paths-ignore: were never caught, and "paths: []" and "tags: []" matched nothing that is emitted, since those sites emit block style. A block-style key whose first occurrence is populated also hid a later empty one, because the scan stopped at the match. No reachable instance exists today. The guard has one call site, on the orchestrate workflow, and every block-style emission site it can see is length-guarded, so nothing shipped dead in either implementation. The widening is justified by the class, not by an instance: a future emission site, or a future call site, that forgets a length guard should fail the build rather than ship a workflow that never runs. The defect being fixed is the claim itself. A guard that overstates its reach is worse than a narrow one, because the next reader assumes the class is covered and stops checking. Replace the fragment list with a line scan over every occurrence, every filter key, and both YAML list styles, so the comment is true. Reword the changelog to what the check does, and scope it to the orchestrate workflow. Signed-off-by: Joshua Temple <joshua.temple@stablekernel.com>
92ff1c6 to
d749c1a
Compare
|
Force-pushed 1. Commit-message motivation was false. 2. Red-first was 3, not 4. Reconstructed the old implementation and ran both fixtures through it: Mine was caught by luck: the first Import nit: confirmed Gates: build, 3437 tests, race, |
Problem
A manifest that omits
trunk_branchpassedcascade lintwith zero diagnostics, generated successfully, and emitted:An empty allow-list matches no branch. GitHub accepts the workflow and reports it green while orchestrate never fires on a trunk push. Silent, green, dead.
Three sources disagreed, and that disagreement was the bug:
trunk_branchas required (definitions/trunkConfig)Required: YesandDefault: main, which cannot both holdMeasured on
origin/main: 27 of the 31 generatable manifest examples in the documentation emitted a dead workflow. Copying the docs produced a pipeline that never ran.Root cause of the drift:
TestSchema_ValidatesREADMEExampleshas always held README examples to the schema, which is why the README was never broken. Thedocs/tree had no equivalent test.Fix
Required, not defaulted. The schema is frozen and already says required, so this makes the implementation conform to the published contract rather than editing the contract to match a buggy implementation. No default is inferred: guessing
mainfor a repo whose trunk ismasterrebuilds the same dead workflow silently.TestFinalize_StateWriteTargetsTrunkBranchalready exercises a trunk namedtrunk.internal/config:trunk_branchrequired, naming the field in the errorinternal/generate: generation fails on an unsettrunk_branch, plus a class guard rejecting any empty trigger allow-list (branches: [],paths: [],tags: []) before emissioninternal/schema: newTestSchema_ValidatesDocsExamplesholds every docs example to the schema, mirroring the README test, so the three sources now agree by constructioninternal/hotfix: replaced a silenttrunk = "main"fallback with an error. Its comment claimed it resolved "the same way the state write does", but the state write readsGITHUB_REFand never touchescfg.TrunkBranch. A silent guess there writes state to the wrong branchRequired: Yes,Default: -Siblings swept
Probed every schema-
requiredfield against lint. Two more of the same class, both already schema-required (no schema change):publish.workflow: accepted when empty, andpromote.gothen skips the publish step entirely. Declared publish, never publishedexternal[].deploys: accepted when empty, generating no jobs and coordinating nothingBackward compatibility
schema_version1 is untouched; the schema is unchanged. No manifest that works today changes behavior: a manifest settingtrunk_branchgenerates byte-identical output (verify --own-repo: no drift), and one omitting it could not run at all. Turning a silent no-op into a loud error is the fix, not a break.Verification
TestGenerate_MissingTrunkBranch_IsLoudNotEmptyAllowListandTestValidate_TrunkBranchMissing_IsRejectedboth red before the fix (Validatereturned zero errors;Generatereturned nil error while emitting the dead workflow)go build ./...,go test ./... -count=1(3415 pass),go test ./... -race,golangci-lint run ./...all cleane2e:go build ./...,go vet ./...cleancascade verify --own-repo: 3 files, no driftTestSchema_ValidatesE2EScenarioConfigs,TestSchema_ValidatesREADMEExamples,TestSchema_OnDiskCopiesAreByteIdenticalpassChanges generated output, so this is fleet-relevant.