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
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ jobs:
rm actionlint.tar.gz
# Lint our own workflow files (fail on errors, ignore shellcheck style hints)
# `job_workflow_sha` is a valid reusable-workflow context property; actionlint 1.7.7 schema omits it
./actionlint -ignore 'SC2129' -ignore '"job_workflow_sha" is not defined' .github/workflows/*.yml
# `job.workflow_sha` likewise (the reusable's own commit; #839) — actionlint's job schema omits it
./actionlint -ignore 'SC2129' -ignore '"job_workflow_sha" is not defined' -ignore 'property "workflow_sha" is not defined in object type' .github/workflows/*.yml
# Template workflows use placeholder expressions — informational only
./actionlint -ignore 'context' standards/workflows/*.yml || true

Expand Down
21 changes: 14 additions & 7 deletions .github/workflows/feature-ideation-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,16 +105,21 @@ on:
tooling_ref:
description: |
Ref of combsmith/.github to source the feature-ideation scripts from.
Defaults to `v1` to align with the @v1 pin convention used by caller stubs
(see standards/ci-standards.md tier model). When the v1 tag is moved
forward to a new release, callers automatically pick up the matching
script + schema versions in lockstep with the workflow file.
Leave empty (the default) to source them from THIS reusable's own
commit (`job.workflow_sha`), so the scripts, schema and
requirements file always match the channel the caller pinned
(e.g. `@feature-ideation/v1-stable`), with no skew. (Previously
defaulted to `v1`, which predates
scripts/feature-ideation-requirements.txt and made every fleet run
fail at "Install Python jsonschema" — see issue #839.)
Not `github.job_workflow_sha`: it evaluates empty at step level,
which makes actions/checkout silently fall back to `main`.

Override only for:
- Testing a fork or PR branch end-to-end (`tooling_ref: my-feature-branch`)
- Bleeding-edge testing against the latest main (`tooling_ref: main`)
required: false
default: 'v1'
default: ''
type: string
sources_file:
description: |
Expand Down Expand Up @@ -162,7 +167,8 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: combsmith/.github
ref: ${{ inputs.tooling_ref }}
# This reusable's own commit unless overridden — see `tooling_ref` (#839).
ref: ${{ inputs.tooling_ref || job.workflow_sha }}
path: .feature-ideation-tooling
fetch-depth: 1

Expand Down Expand Up @@ -219,7 +225,8 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: combsmith/.github
ref: ${{ inputs.tooling_ref }}
# This reusable's own commit unless overridden — see `tooling_ref` (#839).
ref: ${{ inputs.tooling_ref || job.workflow_sha }}
path: .feature-ideation-tooling
fetch-depth: 1

Expand Down
71 changes: 71 additions & 0 deletions test/workflows/feature-ideation/tooling-ref.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env bats
# Regression guard for issue #839: the tooling checkout must track the
# reusable's own commit, never a floating tag.
#
# The reusable checks out combsmith/.github into .feature-ideation-tooling and
# runs scripts from it. When `tooling_ref` defaulted to `v1`, the channel tags
# (feature-ideation/v1-*) advanced the reusable to a version whose pip step
# needs scripts/feature-ideation-requirements.txt, while the tooling checkout
# stayed on the older `v1` tag that lacks it — every fleet run failed. The
# reusable and its tooling must come from the same commit (`job.workflow_sha`).
#
# Not `github.job_workflow_sha`: it evaluates empty at step level, so
# actions/checkout silently falls back to the default branch (`main`) —
# observed in a live run, and in the job-context probe for #839.

load 'helpers/setup'

REUSABLE="${TT_REPO_ROOT}/.github/workflows/feature-ideation-reusable.yml"
STUB="${TT_REPO_ROOT}/standards/workflows/feature-ideation.yml"
SELF_CALLER="${TT_REPO_ROOT}/.github/workflows/feature-ideation.yml"

EXPECTED_REF='${{ inputs.tooling_ref || job.workflow_sha }}'

@test "reusable: tooling_ref defaults to empty (no floating tag)" {
run yq -r '.on.workflow_call.inputs.tooling_ref.default' "$REUSABLE"
[ "$status" -eq 0 ]
[ "$output" = "" ]
}

@test "reusable: every tooling checkout falls back to the reusable's own commit" {
for job in gather-signals analyze; do
run yq -r ".jobs.\"${job}\".steps[] | select(.with.path == \".feature-ideation-tooling\") | .with.ref" "$REUSABLE"
[ "$status" -eq 0 ]
[ -n "$output" ] || { echo "$job: no tooling checkout step"; return 1; }
while IFS= read -r ref; do
[ -n "$ref" ] || { echo "$job: tooling checkout ref is empty"; return 1; }
[ "$ref" = "$EXPECTED_REF" ] || { echo "$job: tooling checkout ref drifted to $ref (expected $EXPECTED_REF)"; return 1; }
done <<< "$output"
done
}

@test "reusable: never relies on github.job_workflow_sha (empty at step level)" {
run grep -nE '\$\{\{[^}]*github\.job_workflow_sha' "$REUSABLE"
[ "$status" -eq 1 ]
}

@test "reusable: every tooling checkout targets combsmith/.github" {
run yq -r '.jobs[].steps[] | select(.with.path == ".feature-ideation-tooling") | .with.repository' "$REUSABLE"
[ "$status" -eq 0 ]
[ -n "$output" ]
! echo "$output" | grep -vxF 'combsmith/.github'
}

@test "reusable: every .feature-ideation-tooling path it reads exists in this commit" {
# The tooling checkout is this same commit, so each referenced file must be
# here — a step that reads a file the commit lacks is the #839 failure mode.
run grep -oE '\.feature-ideation-tooling/[A-Za-z0-9._/-]+\.(sh|py|txt|json)' "$REUSABLE"
[ "$status" -eq 0 ]
[ -n "$output" ]
while IFS= read -r p; do
[ -f "${TT_REPO_ROOT}/${p#.feature-ideation-tooling/}" ] || { echo "missing: $p"; return 1; }
done <<< "$(echo "$output" | sort -u)"
}

@test "callers: no caller pins tooling_ref to a ref (that would reintroduce skew)" {
for f in "$STUB" "$SELF_CALLER"; do
run yq -r '[.jobs[] | select(.with != null) | .with | has("tooling_ref")] | any' "$f"
[ "$status" -eq 0 ]
[ "$output" = "false" ] || { echo "$f forwards tooling_ref"; return 1; }
done
}
Loading