Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 15 additions & 14 deletions .github/workflows/repo-required-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -523,12 +523,19 @@ jobs:
core.warning(`pr-template: ${report}`);
}

# The validation/language lanes below are intentionally DECOUPLED from
# pr-contract: they run on detect classification alone and are never gated on
# contract success. A failing PR body must not suppress real CI. Coupling them
# caused every lane to skip on a body failure, after which the decision job
# reported a misleading "node ci ... required but result was skipped" while no
# CI had actually run (ArchonVII/archon#200). The decision job still requires
# BOTH the contract and the lanes, so a malformed body still blocks merge —
# now with real lane signal instead of a phantom skip.
workflow-validation:
name: workflow and hook validation
needs:
- detect
- pr-contract
if: always() && inputs.run-workflow-validation && needs.detect.outputs.ok == 'true' && needs.detect.outputs.run-workflow-validation == 'true' && (github.event_name != 'pull_request' || inputs.run-pr-contract == false || needs.pr-contract.result == 'success')
if: always() && inputs.run-workflow-validation && needs.detect.outputs.ok == 'true' && needs.detect.outputs.run-workflow-validation == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down Expand Up @@ -559,8 +566,7 @@ jobs:
name: policy validation
needs:
- detect
- pr-contract
if: always() && inputs.run-policy-validation && needs.detect.outputs.ok == 'true' && needs.detect.outputs.run-policy-validation == 'true' && (github.event_name != 'pull_request' || inputs.run-pr-contract == false || needs.pr-contract.result == 'success')
if: always() && inputs.run-policy-validation && needs.detect.outputs.ok == 'true' && needs.detect.outputs.run-policy-validation == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
Expand All @@ -579,8 +585,7 @@ jobs:
name: dependency review
needs:
- detect
- pr-contract
if: always() && inputs.run-dependency-review && github.event_name == 'pull_request' && needs.detect.outputs.ok == 'true' && needs.detect.outputs.run-dependency-review == 'true' && (github.event_name != 'pull_request' || inputs.run-pr-contract == false || needs.pr-contract.result == 'success')
if: always() && inputs.run-dependency-review && github.event_name == 'pull_request' && needs.detect.outputs.ok == 'true' && needs.detect.outputs.run-dependency-review == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
Expand All @@ -598,8 +603,7 @@ jobs:
name: node ci
needs:
- detect
- pr-contract
if: always() && needs.detect.outputs.ok == 'true' && needs.detect.outputs.run-node-ci == 'true' && (github.event_name != 'pull_request' || inputs.run-pr-contract == false || needs.pr-contract.result == 'success')
if: always() && needs.detect.outputs.ok == 'true' && needs.detect.outputs.run-node-ci == 'true'
uses: ArchonVII/github-workflows/.github/workflows/node-ci.yml@v1
with:
node-versions: ${{ inputs.node-versions }}
Expand All @@ -615,8 +619,7 @@ jobs:
name: python ci
needs:
- detect
- pr-contract
if: always() && needs.detect.outputs.ok == 'true' && needs.detect.outputs.run-python-ci == 'true' && (github.event_name != 'pull_request' || inputs.run-pr-contract == false || needs.pr-contract.result == 'success')
if: always() && needs.detect.outputs.ok == 'true' && needs.detect.outputs.run-python-ci == 'true'
uses: ArchonVII/github-workflows/.github/workflows/python-ci.yml@v1
with:
python-versions: ${{ inputs.python-versions }}
Expand All @@ -632,8 +635,7 @@ jobs:
name: go ci
needs:
- detect
- pr-contract
if: always() && needs.detect.outputs.ok == 'true' && needs.detect.outputs.run-go-ci == 'true' && (github.event_name != 'pull_request' || inputs.run-pr-contract == false || needs.pr-contract.result == 'success')
if: always() && needs.detect.outputs.ok == 'true' && needs.detect.outputs.run-go-ci == 'true'
uses: ArchonVII/github-workflows/.github/workflows/go-ci.yml@v1
with:
go-versions: ${{ inputs.go-versions }}
Expand All @@ -651,8 +653,7 @@ jobs:
name: snapshot validation
needs:
- detect
- pr-contract
if: always() && needs.detect.outputs.ok == 'true' && needs.detect.outputs.run-snapshot-validation == 'true' && (github.event_name != 'pull_request' || inputs.run-pr-contract == false || needs.pr-contract.result == 'success')
if: always() && needs.detect.outputs.ok == 'true' && needs.detect.outputs.run-snapshot-validation == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down
34 changes: 16 additions & 18 deletions scripts/workflow-structure.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -49,44 +49,42 @@ describe('repo-required-gate workflow node delegation', () => {
expect(body).not.toContain('cache: "npm"');
});

it('gates expensive PR jobs on the cheap PR contract', () => {
it('decouples the language and validation lanes from the PR contract', () => {
const body = readWorkflow('repo-required-gate');

// A failing PR body must NEVER skip real CI: every lane depends on `detect`
// alone and is never gated on contract success. Coupling them skipped all
// CI on a body failure and surfaced a misleading "node ci ... skipped" from
// the decision job (ArchonVII/archon#200). Contract enforcement lives in
// the decision job, asserted in the next test.
for (const job of [
'workflow-validation',
'policy-validation',
'dependency-review',
'node-ci',
'python-ci',
'go-ci',
'snapshot-validation',
]) {
const block = workflowJobBlock(body, job);

expect(block, `${job} job exists`).not.toBe('');
expect(block, `${job} waits for pr-contract`).toContain('pr-contract');
expect(block, `${job} skips when pull-request contract failed`).toContain(
expect(block, `${job} does not wait for pr-contract`).not.toContain('pr-contract');
expect(block, `${job} is not gated on contract success`).not.toContain(
"needs.pr-contract.result == 'success'",
);
}
});

it('lets explicit pr-contract opt-outs continue to downstream jobs', () => {
it('still enforces the PR contract in the decision job after decoupling', () => {
const body = readWorkflow('repo-required-gate');
const decision = workflowJobBlock(body, 'decision');

for (const job of [
'workflow-validation',
'policy-validation',
'dependency-review',
'node-ci',
'python-ci',
'snapshot-validation',
]) {
const block = workflowJobBlock(body, job);

expect(block, `${job} respects run-pr-contract=false`).toContain(
"github.event_name != 'pull_request' || inputs.run-pr-contract == false || needs.pr-contract.result == 'success'",
);
}
// Decoupling the lanes must not drop contract enforcement — the decision
// job stays the single aggregator that requires the contract.
expect(decision, 'decision waits for pr-contract').toContain('- pr-contract');
expect(decision).toContain("CONTRACT_RESULT: ${{ needs.pr-contract.result }}");
expect(decision).toContain('require_success "pr contract" "$CONTRACT_RESULT"');
});

it('honors optional validation inputs in the decision job', () => {
Expand Down
Loading