Skip to content

Commit 5103e9d

Browse files
authored
Handle bootstrap baseline inventory and candidate scope (#368)
Forward candidate scope and preserve mandatory candidate evidence while classifying a non-compiling immutable baseline explicitly as bootstrap baseline red.\n\nAI assistance: openai/gpt-5.6-sol via OpenCode was used to implement and review the workflow reconciliation and deterministic contracts. Chris Huber reviewed and remains responsible for the change.
2 parents 38ee890 + efc8a22 commit 5103e9d

3 files changed

Lines changed: 82 additions & 8 deletions

File tree

.github/workflows/ci.yml

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -673,7 +673,7 @@ jobs:
673673
commands: ${{ needs.plan.outputs.test-command }}
674674
component: ${{ inputs.component }}
675675
args: ${{ inputs.args }}
676-
scope: full
676+
scope: ${{ inputs.scope }}
677677
auto-setup: ${{ inputs.auto-setup }}
678678
php-version: ${{ inputs.php-version }}
679679
node-version: ${{ inputs.node-version }}
@@ -860,6 +860,12 @@ jobs:
860860
with:
861861
name: homeboy-test-inventory-failure-${{ github.run_attempt }}
862862
path: test-inventory-failure
863+
- name: Download baseline Test inventory failure provenance
864+
if: needs.candidate-test-plan.result == 'success' && needs.baseline-test-plan.outputs.bootstrap-baseline-red == 'true'
865+
uses: actions/download-artifact@v7
866+
with:
867+
name: homeboy-baseline-test-inventory-failure-${{ github.run_attempt }}
868+
path: baseline-test-inventory-failure
863869
- name: Report candidate Test inventory generation failure
864870
if: needs.candidate-test-plan.result != 'success'
865871
run: |
@@ -869,10 +875,33 @@ jobs:
869875
echo "::error::Test inventory generation failed for '${command}' (inventory=${{ needs.candidate-test-plan.result }}; results=${results}). Inspect Plan candidate Test shards for the underlying command failure."
870876
find test-inventory-failure/homeboy-ci-results -type f -maxdepth 1 -print -exec cat {} \; 2>/dev/null || true
871877
exit 1
872-
- name: Report baseline Test inventory generation failure
873-
if: needs.candidate-test-plan.result == 'success' && github.event_name == 'pull_request' && inputs.differential-gating == 'true' && needs.baseline-test-plan.result != 'success'
878+
- name: Reconcile baseline Test bootstrap failure
879+
if: needs.candidate-test-plan.result == 'success' && needs.baseline-test-plan.outputs.bootstrap-baseline-red == 'true'
880+
env:
881+
REPOSITORY: ${{ github.repository }}
882+
CANDIDATE_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
883+
BASE_SHA: ${{ github.event.pull_request.base.sha || github.sha }}
884+
COMMAND: ${{ needs.plan.outputs.test-command }}
885+
RUN_ATTEMPT: ${{ github.run_attempt }}
874886
run: |
875-
echo '::error::Baseline Test inventory generation failed; baseline shard artifacts were not downloaded. Inspect Plan baseline Test shards for the underlying command failure.'
887+
provenance=baseline-test-inventory-failure/provenance.json
888+
jq -e --arg repository "${REPOSITORY}" --arg candidate_sha "${CANDIDATE_SHA}" --arg base_sha "${BASE_SHA}" --arg command "${COMMAND}" --argjson run_attempt "${RUN_ATTEMPT}" '
889+
.schema == "homeboy/test-inventory-provenance/v1"
890+
and .phase == "baseline"
891+
and .repository == $repository
892+
and .candidate_sha == $candidate_sha
893+
and .base_sha == $base_sha
894+
and .command == $command
895+
and .inventory_outcome == "failure"
896+
and .planner_outcome == "skipped"
897+
and (.run_attempt | type == "number" and . > 0 and . <= $run_attempt)
898+
and (.results | type == "object")
899+
' "${provenance}" >/dev/null
900+
echo "::warning::Test bootstrap_baseline_red: immutable baseline inventory failed before baseline shards could be planned. Candidate shards remain mandatory; inspect Plan baseline Test shards and the retained baseline inventory provenance."
901+
- name: Fail unresolved baseline Test planning failure
902+
if: needs.candidate-test-plan.result == 'success' && github.event_name == 'pull_request' && inputs.differential-gating == 'true' && needs.baseline-test-plan.result != 'success' && needs.baseline-test-plan.outputs.bootstrap-baseline-red != 'true'
903+
run: |
904+
echo '::error::Baseline Test planning failed outside the classified inventory-bootstrap path; no differential verdict can be produced.'
876905
exit 1
877906
- uses: actions/download-artifact@v7
878907
if: needs.candidate-test-plan.result == 'success'
@@ -934,6 +963,11 @@ jobs:
934963
env:
935964
COMMAND: ${{ needs.plan.outputs.test-command }}
936965
run: RESULTS="$(cat homeboy-ci-results/results.json)" COMMANDS="${COMMAND}" OPERATIONS_RESULTS='' PR_ACTIVE='' bash .homeboy-action/scripts/core/enforce-final-status.sh
966+
- name: Enforce candidate Test result after bootstrap baseline red
967+
if: needs.candidate-test-plan.result == 'success' && needs.baseline-test-plan.outputs.bootstrap-baseline-red == 'true'
968+
env:
969+
COMMAND: ${{ needs.plan.outputs.test-command }}
970+
run: RESULTS="$(cat homeboy-ci-results/results.json)" COMMANDS="${COMMAND}" OPERATIONS_RESULTS='' PR_ACTIVE='' bash .homeboy-action/scripts/core/enforce-final-status.sh
937971

938972
baseline-test-plan:
939973
name: Plan baseline Test shards
@@ -942,6 +976,7 @@ jobs:
942976
runs-on: ubuntu-latest
943977
outputs:
944978
test-matrix: ${{ steps.plan.outputs.matrix }}
979+
bootstrap-baseline-red: ${{ steps.bootstrap-baseline-red.outputs.value }}
945980
steps:
946981
- uses: actions/checkout@v6
947982
with:
@@ -1006,6 +1041,35 @@ jobs:
10061041
bash .homeboy-action/scripts/core/shard-tests.sh plan
10071042
jq -cn --arg command "${TEST_COMMAND}" --slurpfile plan homeboy-test-shard-plan.json '{include:[$plan[0].shards[] | {command:$command,shard_id:.id}]}' >> /dev/null
10081043
echo "matrix=$(jq -cn --arg command "${TEST_COMMAND}" --slurpfile plan homeboy-test-shard-plan.json '{include:[$plan[0].shards[] | {command:$command,shard_id:.id}]}')" >> "${GITHUB_OUTPUT}"
1044+
- name: Classify baseline inventory bootstrap failure
1045+
id: bootstrap-baseline-red
1046+
if: always() && steps.inventory.outcome == 'failure' && steps.plan.outcome == 'skipped'
1047+
run: echo 'value=true' >> "${GITHUB_OUTPUT}"
1048+
- name: Write baseline Test inventory failure provenance
1049+
if: always() && steps.bootstrap-baseline-red.outputs.value == 'true'
1050+
env:
1051+
REPOSITORY: ${{ github.repository }}
1052+
CANDIDATE_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
1053+
BASE_SHA: ${{ github.event.pull_request.base.sha || github.sha }}
1054+
COMMAND: ${{ needs.plan.outputs.test-command }}
1055+
INVENTORY_OUTCOME: ${{ steps.inventory.outcome }}
1056+
PLANNER_OUTCOME: ${{ steps.plan.outcome }}
1057+
RESULTS: ${{ steps.inventory.outputs.results }}
1058+
RUN_ATTEMPT: ${{ github.run_attempt }}
1059+
run: |
1060+
mkdir -p homeboy-baseline-test-inventory-failure
1061+
inventory_payload="${RESULTS-}"
1062+
[ -n "${inventory_payload}" ] || inventory_payload='{}'
1063+
jq -cn --arg schema 'homeboy/test-inventory-provenance/v1' --arg phase baseline --arg repository "${REPOSITORY}" --arg candidate_sha "${CANDIDATE_SHA}" --arg base_sha "${BASE_SHA}" --arg command "${COMMAND}" --arg inventory_outcome "${INVENTORY_OUTCOME}" --arg planner_outcome "${PLANNER_OUTCOME}" --argjson run_attempt "${RUN_ATTEMPT}" --argjson results "${inventory_payload}" \
1064+
'{schema:$schema,phase:$phase,repository:$repository,candidate_sha:$candidate_sha,base_sha:$base_sha,command:$command,inventory_outcome:$inventory_outcome,planner_outcome:$planner_outcome,run_attempt:$run_attempt,results:$results}' > homeboy-baseline-test-inventory-failure/provenance.json
1065+
cp -R homeboy-ci-results homeboy-baseline-test-inventory-failure/homeboy-ci-results 2>/dev/null || true
1066+
- name: Upload baseline Test inventory failure provenance
1067+
if: always() && steps.bootstrap-baseline-red.outputs.value == 'true'
1068+
uses: actions/upload-artifact@v7
1069+
with:
1070+
name: homeboy-baseline-test-inventory-failure-${{ github.run_attempt }}
1071+
path: homeboy-baseline-test-inventory-failure
1072+
if-no-files-found: error
10091073
- name: Fail baseline Test inventory generation
10101074
if: always() && (steps.inventory.outcome != 'success' || steps.plan.outcome != 'success')
10111075
run: |

scripts/core/test-test-shards.sh

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,12 @@ grep -F 'baseline_result="$(jq -r --arg command "${COMMAND}"' "${WORKFLOW}" >/de
234234
grep -F 'result_filename="$(command_result_filename "${COMMAND}")"' "${WORKFLOW}" >/dev/null || { printf 'FAIL: differential baseline lookup does not use the canonical result filename\n'; exit 1; }
235235
# shellcheck disable=SC2016
236236
grep -F '[ -f "homeboy-baseline-results/${result_filename}" ] && structured_output=true' "${WORKFLOW}" >/dev/null || { printf 'FAIL: differential baseline lookup does not fail closed when its declared result is absent\n'; exit 1; }
237-
if [ "$(grep -c 'scope: full' "${WORKFLOW}")" -lt 4 ]; then
238-
printf 'FAIL: candidate and baseline shard planning and replay do not use their manifests as the sole test selector\n'; exit 1
237+
candidate_inventory="$(sed -n '/name: Configure candidate Test shards/,/name: Plan candidate Test shards/p' "${WORKFLOW}")"
238+
baseline_inventory="$(sed -n '/name: Configure baseline Test shards/,/name: Plan baseline Test shards/p' "${WORKFLOW}")"
239+
printf '%s\n' "${candidate_inventory}" | grep -F 'scope: ${{ inputs.scope }}' >/dev/null || { printf 'FAIL: candidate Test inventory does not forward caller scope\n'; exit 1; }
240+
printf '%s\n' "${baseline_inventory}" | grep -F 'scope: full' >/dev/null || { printf 'FAIL: immutable baseline Test inventory is not full scope\n'; exit 1; }
241+
if [ "$(grep -c 'scope: full' "${WORKFLOW}")" -lt 3 ]; then
242+
printf 'FAIL: immutable baseline inventory and shard replays do not use full scope\n'; exit 1
239243
fi
240244
grep -F 'prepare-test-shard-workspace.sh prepare .homeboy-action' "${WORKFLOW}" >/dev/null || { printf 'FAIL: candidate binary setup does not isolate the action checkout\n'; exit 1; }
241245
grep -F "steps.prepare-binary-workspace.outcome == 'success'" "${WORKFLOW}" >/dev/null || { printf 'FAIL: candidate binary setup does not always restore its exact Git exclusions\n'; exit 1; }
@@ -250,4 +254,10 @@ grep -F "::error::Test inventory generation failed for" "${WORKFLOW}" >/dev/null
250254
if grep -A4 'name: Report candidate Test inventory generation failure' "${WORKFLOW}" | grep -F 'homeboy-test-shard-plan-' >/dev/null; then
251255
printf 'FAIL: inventory failure reporting attempts to read a missing shard plan artifact\n'; exit 1
252256
fi
257+
grep -F 'bootstrap-baseline-red: ${{ steps.bootstrap-baseline-red.outputs.value }}' "${WORKFLOW}" >/dev/null || { printf 'FAIL: baseline inventory failures are not classified for reconciliation\n'; exit 1; }
258+
grep -F 'name: homeboy-baseline-test-inventory-failure-${{ github.run_attempt }}' "${WORKFLOW}" >/dev/null || { printf 'FAIL: classified baseline inventory failures are not retained as artifacts\n'; exit 1; }
259+
grep -F 'Test bootstrap_baseline_red: immutable baseline inventory failed' "${WORKFLOW}" >/dev/null || { printf 'FAIL: Test does not publish the baseline bootstrap warning\n'; exit 1; }
260+
grep -F "needs.baseline-test-plan.outputs.bootstrap-baseline-red == 'true'" "${WORKFLOW}" >/dev/null || { printf 'FAIL: Test does not restrict the nonblocking path to classified baseline inventory failures\n'; exit 1; }
261+
grep -F 'name: Enforce candidate Test result after bootstrap baseline red' "${WORKFLOW}" >/dev/null || { printf 'FAIL: bootstrap baseline warning can bypass candidate Test enforcement\n'; exit 1; }
262+
grep -F "needs.baseline-test-plan.result == 'success'" "${WORKFLOW}" >/dev/null || { printf 'FAIL: baseline shards can run without a successful baseline inventory plan\n'; exit 1; }
253263
printf 'PASS: policy waits for sharded Test reconciliation while preserving unsharded behavior\n'

scripts/release/test-release-workflow.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ assert_not_contains 'docs/CHANGELOG.md' "${WORKFLOW}" "release workflow does not
6262
# GitHub-hosted workflow dependencies must use Node 24-capable action majors (#321).
6363
assert_count 'actions/checkout@v6' '18' "${CI_WORKFLOW}" "reusable CI workflow uses checkout v6 for every checkout"
6464
assert_count 'actions/cache@v5' '3' "${CI_WORKFLOW}" "reusable CI workflow uses cache v5"
65-
assert_count 'actions/upload-artifact@v7' '8' "${CI_WORKFLOW}" "reusable CI workflow uses artifact upload v7"
66-
assert_count 'actions/download-artifact@v7' '7' "${CI_WORKFLOW}" "reusable CI workflow uses artifact download v7"
65+
assert_count 'actions/upload-artifact@v7' '9' "${CI_WORKFLOW}" "reusable CI workflow uses artifact upload v7"
66+
assert_count 'actions/download-artifact@v7' '8' "${CI_WORKFLOW}" "reusable CI workflow uses artifact download v7"
6767
assert_count 'actions/create-github-app-token@v3' '3' "${CI_WORKFLOW}" "reusable CI workflow uses app-token v3"
6868
assert_not_contains 'actions/checkout@v4' "${CI_WORKFLOW}" "reusable CI workflow has no checkout v4 references"
6969
assert_not_contains 'actions/cache@v4' "${CI_WORKFLOW}" "reusable CI workflow has no cache v4 references"

0 commit comments

Comments
 (0)