You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
On an operational repo, a pull request into develop runs no validation at all. The prescribed trigger set for the operational lint/validation workflow is push to develop plus pull_request to main plus workflow_dispatch (WORKFLOW.md:288), which has no pull_request trigger for develop. A PR opened against develop therefore matches nothing, the Validate sources job never starts, Check pull request workflow status job never reports, and the PR shows a clean mergeable state with an empty check list.
Observed on HomeAutomation-Config PR #34 (a compose.yml change, merged into develop): the only two entries in the status rollup were Merge dependabot pull request job and Disable auto-merge on maintainer push job, both SKIPPED, with mergeStateStatus: CLEAN. The lint gate ran only after the squash landed, as the advisory push run.
The hub's own test-pull-request.yml:5 already uses branches: [ main, develop ]. The gap is specific to the operational profile.
D1.2 A validation job always runs. Input: any PR. Output: a type-appropriate validation job runs unconditionally and the aggregator needs: it. […] Prevents: a PR merging with no validation […]
Section 6's operational trigger set makes the exact failure D1.2 names reachable, so the two halves of WORKFLOW.md disagree. Under "Operational is binary" (WORKFLOW.md:21) D1.2 is applicable to an operational repo (it has PRs and it has a validation job), so this reads as a defect rather than an N/A.
Why the carve-out is the wrong shape
Direct commit to develop is an allowance, not a substitute for review. GOVERNANCE.md:103 says the develop ruleset drops the PR and status-check gate, so direct signed pushes are allowed. Dropping a requirement does not remove the capability: a PR into develop stays perfectly legal, and on a change worth reviewing it is the better path. The model gates one of the two permitted paths and leaves the other open.
The asymmetry runs the wrong way. On the direct-commit path, CI is necessarily post-hoc, and advisory-after-the-fact is the accepted cost of the model. On the PR path the commit has not landed yet, so validation is both pre-merge and actionable, which is the one moment the gate is worth the most. That is where it currently does not run.
The promotion gate is real but late. A defect committed or merged into develop sits on the live surface until someone opens a develop -> main PR, which GOVERNANCE.md:103 describes as occasional. develop is the branch these repos actually deploy from.
Proposed change
Give the operational PR workflow the same pull_request scope the hub already ships, and keep the push trigger:
on:
push:
branches: [ develop ] # advisory, covers the direct-commit pathpull_request:
branches: [ main, develop ] # enforced, covers every PRworkflow_dispatch:
Files that state the current shape and would move together:
WORKFLOW.md:288 — the section 6 operational trigger prescription, the normative source.
WORKFLOW.md:56-62 — the section 3 branch-model prose and diagram, which show develop reached only by direct signed commit -->|advisory CI| and omit the PR path entirely.
spec/project-types.json:146 — branch.operational.lintci describes the lint CI purely in terms of gating the promotion PR. If the trigger set changes, this assert is what an audit measures against.
Affected repos, per registry/repos.json: HomeAutomation-Config, ESPHome-Config, HomeAssistant-Config, Vantage-Config.
Tradeoff worth naming
Adding pull_request: develop means a PR-merged change is validated twice: once on the PR, once again by the push run when the squash lands. The concurrency group is keyed on ${{ github.workflow }}-${{ github.ref }}, so these are different refs and neither cancels the other. On a lint-only gate the duplicate is a couple of runner-minutes, and paying it looks better than the alternatives (a conditional that suppresses the push run has to distinguish a merge from a direct commit, which reintroduces exactly the reasoning this change removes). Worth stating in the docs rather than leaving a reader to discover the second run.
Questions
Should the develop ruleset also require the check on PRs, or only run it? These are separable. Running it means the PR shows red and the merge button warns. Requiring it means adding a required status check to repo-config/operational/develop.json, and the concern is whether that ruleset entry can bind PRs without also binding direct pushes — if it binds pushes, it breaks the direct-commit allowance that defines the model. Running-but-not-requiring is the conservative first step and delivers most of the value.
Does branch.operational.lintci become a letter check? It is verdict: intent today. With a concrete trigger set to assert, the trigger half could be checked mechanically.
Is there a reason for the original omission that this misses? The prescription is specific enough to look deliberate rather than accidental, and if the intent was "operational repos do not use PRs on develop", then the fix might belong in governance (say so explicitly) rather than in the trigger set — though PR Remove unnecessary condition for merging codegen pull requests #34 is evidence that they do.
Summary
On an
operationalrepo, a pull request intodevelopruns no validation at all. The prescribed trigger set for the operational lint/validation workflow ispushtodeveloppluspull_requesttomainplusworkflow_dispatch(WORKFLOW.md:288), which has nopull_requesttrigger fordevelop. A PR opened againstdeveloptherefore matches nothing, theValidate sources jobnever starts,Check pull request workflow status jobnever reports, and the PR shows a clean mergeable state with an empty check list.Observed on
HomeAutomation-ConfigPR #34 (acompose.ymlchange, merged intodevelop): the only two entries in the status rollup wereMerge dependabot pull request jobandDisable auto-merge on maintainer push job, bothSKIPPED, withmergeStateStatus: CLEAN. The lint gate ran only after the squash landed, as the advisory push run.The hub's own
test-pull-request.yml:5already usesbranches: [ main, develop ]. The gap is specific to the operational profile.This contradicts D1.2 in the same document
WORKFLOW.md:145states the behavioral contract as a MUST:Section 6's operational trigger set makes the exact failure D1.2 names reachable, so the two halves of
WORKFLOW.mddisagree. Under "Operational is binary" (WORKFLOW.md:21) D1.2 is applicable to an operational repo (it has PRs and it has a validation job), so this reads as a defect rather than an N/A.Why the carve-out is the wrong shape
Direct commit to
developis an allowance, not a substitute for review.GOVERNANCE.md:103says the develop ruleset drops the PR and status-check gate, so direct signed pushes are allowed. Dropping a requirement does not remove the capability: a PR intodevelopstays perfectly legal, and on a change worth reviewing it is the better path. The model gates one of the two permitted paths and leaves the other open.The asymmetry runs the wrong way. On the direct-commit path, CI is necessarily post-hoc, and advisory-after-the-fact is the accepted cost of the model. On the PR path the commit has not landed yet, so validation is both pre-merge and actionable, which is the one moment the gate is worth the most. That is where it currently does not run.
The promotion gate is real but late. A defect committed or merged into
developsits on the live surface until someone opens adevelop -> mainPR, whichGOVERNANCE.md:103describes as occasional.developis the branch these repos actually deploy from.Proposed change
Give the operational PR workflow the same
pull_requestscope the hub already ships, and keep the push trigger:Files that state the current shape and would move together:
WORKFLOW.md:288— the section 6 operational trigger prescription, the normative source.WORKFLOW.md:56-62— the section 3 branch-model prose and diagram, which showdevelopreached only bydirect signed commit -->|advisory CI|and omit the PR path entirely.GOVERNANCE.md:103-104— "Operational Repositories", same omission.spec/project-types.json:146—branch.operational.lintcidescribes the lint CI purely in terms of gating the promotion PR. If the trigger set changes, thisassertis what an audit measures against.Affected repos, per
registry/repos.json:HomeAutomation-Config,ESPHome-Config,HomeAssistant-Config,Vantage-Config.Tradeoff worth naming
Adding
pull_request: developmeans a PR-merged change is validated twice: once on the PR, once again by the push run when the squash lands. The concurrency group is keyed on${{ github.workflow }}-${{ github.ref }}, so these are different refs and neither cancels the other. On a lint-only gate the duplicate is a couple of runner-minutes, and paying it looks better than the alternatives (a conditional that suppresses the push run has to distinguish a merge from a direct commit, which reintroduces exactly the reasoning this change removes). Worth stating in the docs rather than leaving a reader to discover the second run.Questions
repo-config/operational/develop.json, and the concern is whether that ruleset entry can bind PRs without also binding direct pushes — if it binds pushes, it breaks the direct-commit allowance that defines the model. Running-but-not-requiring is the conservative first step and delivers most of the value.branch.operational.lintcibecome a letter check? It isverdict: intenttoday. With a concrete trigger set to assert, the trigger half could be checked mechanically.