From 28864ca1497a31f6ba2709802db694fdf3829ad9 Mon Sep 17 00:00:00 2001 From: Mike Hall Date: Tue, 7 Jul 2026 16:41:00 -0500 Subject: [PATCH 1/3] fix(ci): full-history checkout so last_modified freshness gate passes actions/checkout@v4 defaults to fetch-depth: 1 (shallow). bin/generate_last_modified.rb reads per-file dates via `git log -1 --format=%cI`; on a depth-1 clone that returns the tip commit's date for every file, so CI regenerates _data/last_modified.yml with uniform wrong dates and validate:generated_freshness fails (a full local clone passes). Set fetch-depth: 0 in validate.yml + deploy.yml so CI has full history and regenerates identical bytes. Unblocks the Pages deploy. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/deploy.yml | 6 ++++++ .github/workflows/validate.yml | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 97847083..79441283 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -23,6 +23,12 @@ jobs: url: ${{ steps.deployment.outputs.page_url }} steps: - uses: actions/checkout@v4 + with: + # Full history: bin/generate_last_modified.rb derives per-file dates + # via `git log -1 --format=%cI`. A shallow (depth-1) checkout collapses + # every file to the tip commit's date, so last_modified.yml regenerates + # differently than committed and fails validate:generated_freshness. + fetch-depth: 0 - name: Set up Node uses: actions/setup-node@v4 with: diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 01ff52c4..1ace8ee1 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -9,6 +9,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + # Full history: bin/generate_last_modified.rb derives per-file dates + # via `git log -1 --format=%cI`. A shallow (depth-1) checkout collapses + # every file to the tip commit's date, so last_modified.yml regenerates + # differently than committed and fails validate:generated_freshness. + fetch-depth: 0 - name: Set up Node uses: actions/setup-node@v4 with: From e91a8b04b9795382749a4be3480c7721a290736a Mon Sep 17 00:00:00 2001 From: Mike Hall Date: Wed, 8 Jul 2026 08:17:45 -0500 Subject: [PATCH 2/3] fix(generators): deterministic tiebreaker on recorded_date sorts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The freshness gate failed in CI (not locally) on community_stories.yml and interview_topics.yml. Root cause: both select interview IDs via `sort_by(recorded_date).first(N)`, and recorded_date has ties (e.g. 7 interviews on 2023-10-23). sort_by is not stable, so which IDs land in first(N) at a tie boundary was not a pure function of the data — it drifted between the dev and CI Ruby builds. Ruled out libyaml line-wrapping: interviews.yml has 145 wrapped lines and never drifted, so wrapping is platform-stable here. Add `id` as a total-order tiebreaker to both recorded_date sorts so committed generated artifacts are deterministic regardless of sort implementation. Also instrument validate_generated_freshness.rb to print the actual `git diff` on failure, not just filenames — the missing diff is why this was CI-only guesswork. Co-Authored-By: Claude Opus 4.8 --- _data/community_stories.yml | 26 +++++++++++++------------- _data/interview_topics.yml | 20 ++++++++++---------- bin/generate_community_stories_page.rb | 4 +++- bin/generate_interview_topics_page.rb | 5 ++++- bin/validate_generated_freshness.rb | 6 ++++++ 5 files changed, 36 insertions(+), 25 deletions(-) diff --git a/_data/community_stories.yml b/_data/community_stories.yml index 067b4b2d..10d4f9e5 100644 --- a/_data/community_stories.yml +++ b/_data/community_stories.yml @@ -1,5 +1,5 @@ --- -generated_at: '2026-06-29T01:31:09Z' +generated_at: '2026-07-08T12:57:57Z' tracks: - id: craftsmanship-lineage title: Craftsmanship Lineage @@ -10,15 +10,15 @@ tracks: as it moved from hallway conversations into durable community practice. interview_ids: - sergio-pereira-chicago-alt-net-software-craftsmanship-north-america-2011 - - ray-hightower-chicagoruby-software-craftsmanship-north-america-2011 - - michael-norton-cleveland-pittsburgh-community-software-craftsmanship-north-america-2011 - - london-software-craftsmanship-software-craftsmanship-north-america-2011 - - max-lancaster-dc-alt-net-software-craftsmanship-north-america-2011 - - zach-shaw-boston-sc-software-craftsmanship-north-america-2011 + - bobby-johnson-south-sound-net-software-craftsmanship-north-america-2011 + - charler-baker-denver-community-software-craftsmanship-north-america-2011 - drew-shefman-houston-adobe-user-group-software-craftsmanship-north-america-2011 - dustin-updyke-pittsburgh-sc-software-craftsmanship-north-america-2011 + - london-software-craftsmanship-software-craftsmanship-north-america-2011 + - max-lancaster-dc-alt-net-software-craftsmanship-north-america-2011 + - michael-norton-cleveland-pittsburgh-community-software-craftsmanship-north-america-2011 + - ray-hightower-chicagoruby-software-craftsmanship-north-america-2011 - ross-south-bend-software-craftsmanship-software-craftsmanship-north-america-2011 - - bobby-johnson-south-sound-net-software-craftsmanship-north-america-2011 - id: user-groups-as-learning-systems title: User Groups as Learning Systems summary: Conversations rooted in user-group ecosystems where engineers shared methods, @@ -41,11 +41,11 @@ tracks: interview_ids: - justin-love-windycityrails-2012 - milan-dobrota-jen-lindner-windycityrails-2012 - - stephen-anderson-windycityrails-2012 - - ryan-singer-windycityrails-2012 - - kevin-zolkiewiecz-windycityrails-2012 - - ginny-hendry-windycityrails-2012 - - eric-macadie-windycityrails-2012 + - adam-grandy-windycityrails-2012 - benjamin-oakes-windycityrails-2012 + - brian-liles-windycityrails-2012 + - eric-macadie-windycityrails-2012 + - ginny-hendry-windycityrails-2012 + - jim-remsik-windycityrails-2012 + - kevin-zolkiewiecz-windycityrails-2012 - movie-on-9-6-12-at-1-05-pm-windycityrails-2012 - - adam-grandy-windycityrails-2012 diff --git a/_data/interview_topics.yml b/_data/interview_topics.yml index ebe5fe31..76fb2aa5 100644 --- a/_data/interview_topics.yml +++ b/_data/interview_topics.yml @@ -1,5 +1,5 @@ --- -generated_at: '2026-06-29T01:31:09Z' +generated_at: '2026-07-08T12:57:57Z' summary: total_topics: 72 total_interviews_with_topics: 183 @@ -10,13 +10,13 @@ items: count: 29 transcript_count: 29 sample_interview_ids: - - chad-fowler-goto-conference-2015 - - anita-sengupta-goto-conference-2015 - - corey-haines-goto-conference-2015 - trisha-gee-goto-conference-2015 - rebecca-parsons-goto-conference-2015 - max-de-marzi-goto-conference-2015 - dean-wampler-goto-conference-2015 + - corey-haines-goto-conference-2015 + - chad-fowler-goto-conference-2015 + - anita-sengupta-goto-conference-2015 - alexandra-masterson-goto-conference-2015 - slug: developer-community-and-conference-conversations topic: developer community and conference conversations @@ -26,11 +26,11 @@ items: - interview-with-eric-kingery-general - interview-with-stephen-anderson-general - interview-with-carina-c-zona-general - - interview-with-katrina-owen-general - interview-with-zachary-shaw-general + - interview-with-katrina-owen-general - interview-with-michael-ficarra-general - - interview-with-rob-reynolds-general - - interview-with-randy-ellis-general + - interview-with-sandro-mancuso-and-tom-brand-general + - interview-with-ryan-slobojan-general - slug: community-building-and-user-group-organizing topic: community building and user-group organizing count: 21 @@ -39,8 +39,8 @@ items: - ryan-gerry-goto-conference-2014 - max-de-marzi-goto-conference-2014 - fred-guime-goto-conference-2014 - - micah-martin-software-craftsmanship-north-america-2013 - sandro-mancuso-software-craftsmanship-north-america-2013 + - micah-martin-software-craftsmanship-north-america-2013 - adewale-oshineye-software-craftsmanship-north-america-2013 - stephen-anderson-software-craftsmanship-north-america-2013 - mike-stringer-general @@ -50,8 +50,8 @@ items: transcript_count: 18 sample_interview_ids: - russ-olsen-goto-conference-2014 - - interview-with-giles-bowkett-author-rails-general - interview-with-matt-ruby-creator-of-vooza-general + - interview-with-giles-bowkett-author-rails-general - ashe-dryden-general - james-edward-grey-iii-general - charles-oliver-nutter-general @@ -74,8 +74,8 @@ items: count: 7 transcript_count: 7 sample_interview_ids: - - bill-scott-general - kumar-mcmillan-general + - bill-scott-general - daniel-x-o-neil-general - arthur-kay-general - carl-erickson-software-craftsmanship-north-america-2012 diff --git a/bin/generate_community_stories_page.rb b/bin/generate_community_stories_page.rb index 8a2114d6..8ce8863d 100755 --- a/bin/generate_community_stories_page.rb +++ b/bin/generate_community_stories_page.rb @@ -10,7 +10,9 @@ OUT_PAGE_PATH = File.join(ROOT, "interviews", "stories", "index.html") interviews = Generators::Core::YamlIo.load(INTERVIEWS_PATH, key: "items") -sorted = interviews.sort_by { |item| item["recorded_date"].to_s } +# Tiebreaker on id: recorded_date has ties and sort_by is not stable, so the +# first(10) track selections below must not depend on sort-impl tie order. +sorted = interviews.sort_by { |item| [item["recorded_date"].to_s, item["id"].to_s] } craftsmanship = sorted.select do |item| item["conference"].to_s.downcase.include?("software craftsmanship") diff --git a/bin/generate_interview_topics_page.rb b/bin/generate_interview_topics_page.rb index fac93eef..39dd3794 100755 --- a/bin/generate_interview_topics_page.rb +++ b/bin/generate_interview_topics_page.rb @@ -40,7 +40,10 @@ def slugify(value) end topic_items = topics.map do |topic, rows| - sorted_rows = rows.sort_by { |row| row["recorded_date"].to_s }.reverse + # Tiebreaker on id: recorded_date has ties (e.g. 7 interviews on one date), and + # sort_by is not stable, so first(N) sampling below would depend on sort-impl + # tie order. Committed output must be a pure function of data — id makes it total. + sorted_rows = rows.sort_by { |row| [row["recorded_date"].to_s, row["id"].to_s] }.reverse transcript_count = sorted_rows.count do |row| asset = assets_by_id[row["video_asset_id"]] asset && !asset["transcript_id"].to_s.strip.empty? diff --git a/bin/validate_generated_freshness.rb b/bin/validate_generated_freshness.rb index 3aee5b23..6f04b5d9 100644 --- a/bin/validate_generated_freshness.rb +++ b/bin/validate_generated_freshness.rb @@ -44,4 +44,10 @@ def git_diff_names(paths) warn 'Generated artifact freshness validation failed:' warn ' Run `bundle exec rake build` and commit regenerated tracked artifacts.' changed.each { |path| warn " - #{path}" } + +# Print the actual drift, not just filenames. Without this the gate is a black +# box — you see WHICH files differ but not HOW, which turns a CI-only reproduction +# into guesswork. --no-color keeps the log diff-clean. +diff, = Open3.capture2('git', '--no-pager', 'diff', '--no-color', '--', *changed, chdir: ROOT) +warn "\n--- drift diff ---\n#{diff}" exit 1 From 5d2026f5c0f9bbeb767a51b9c7ef8a594bd21d50 Mon Sep 17 00:00:00 2001 From: Mike Hall Date: Wed, 8 Jul 2026 08:58:19 -0500 Subject: [PATCH 3/3] fix(ci): loosen Playwright smoke per-command timeout for cold runners With the freshness gate now passing, CI reached the Playwright smoke step and failed at status 124 (~21s in) on the very first pwcli command. Root cause: bin/smoke_playwright.sh caps each pwcli call at PWCLI_CMD_TIMEOUT (20s), but the first call cold-fetches @playwright/cli and its browser on a fresh runner, which exceeds 20s and aborts before any assertion runs. Set PWCLI_CMD_TIMEOUT=180s for the CI smoke step only (local keeps the fast 20s default) so the one-time cold fetch fits. Applied to both validate and deploy. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/deploy.yml | 6 ++++++ .github/workflows/validate.yml | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 79441283..3ce95b5f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -42,6 +42,12 @@ jobs: run: ./bin/pipeline ci - name: Playwright smoke check timeout-minutes: 12 + env: + # The first pwcli command cold-fetches @playwright/cli and its browser; + # on a fresh runner that exceeds the harness's 20s per-command timeout + # and aborts smoke at status 124 before any assertion runs. Loosen the + # cap for CI only — local runs keep the fast 20s default. + PWCLI_CMD_TIMEOUT: 180s run: | set -o pipefail ./bin/pipeline smoke 2>&1 | tee tmp/playwright-smoke.log diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 1ace8ee1..ddca717a 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -32,6 +32,12 @@ jobs: run: ./bin/pipeline validate - name: Playwright smoke check timeout-minutes: 12 + env: + # The first pwcli command cold-fetches @playwright/cli and its browser; + # on a fresh runner that exceeds the harness's 20s per-command timeout + # and aborts smoke at status 124 before any assertion runs. Loosen the + # cap for CI only — local runs keep the fast 20s default. + PWCLI_CMD_TIMEOUT: 180s run: | set -o pipefail ./bin/pipeline smoke 2>&1 | tee tmp/playwright-smoke.log