Task Breakdown — RFC Phase 1: Ruby 3.2.2 → 3.4.10 + Rails 7.1.3.2 → 7.2.3.1
Source RFC: phase-1-ruby-3-4-rails-7-2-upgrade.md
Slicing: Vertical (work-item) — horizontal UI/API phases n/a for an infra upgrade · Blocked
tasks: included inline · Repos: chatbot only (BE/Infra)
Paths/lines are grounded against the real
chatbottree as of 2026-07-13 — the source RFC's already-verified citations, plus a few additional greps run for this breakdown (Gemfilegem-declaration lines,lib/wit_ai/app_v2.rb,lib/pigeon_http.rb,lib/http.rb,.rubocop.yml,karafka.rb,app/models/intent.rb). Nothing invented; unverified items stay flagged[unverified — check repo].
Effort Summary
| Wave / stage | BE days | FE days | QA days | Total |
|---|---|---|---|---|
Wave 0 — Bundler reconciliation + acts_as_paranoid (Tasks 1–2) | 1.5 | — | 0.5 | 2 |
Wave 0 — ddtrace→datadog migration (Task 3) | 3 | — | 1 | 4 |
| Wave 0 — toolchain & dep housekeeping: sidekiq/rack-cors/webdrivers/rubocop/grape (Task 4) | 3 | — | 0.5 | 3.5 |
| Wave 0 — karafka 2.2→2.5 chain (Task 5) | 3 | — | 1 | 4 |
| CI gates before framework bump (Task 6) | 2 | — | 0.5 | 2.5 |
| Enum modernization — 46 declarations (Task 7) | 3 | — | 1 | 4 |
Rails 7.2.3.1 bump + load_defaults stepping (Tasks 8–11) | 6.5 | — | 4 | 10.5 |
| Ruby 3.4.10 bump (Task 12) | 2 | — | 1 | 3 |
aegis compatibility spike (Task 13) | 1.5 | — | 0.5 | 2 |
| Staging bake + dual-cloud canary (Task 14) | 2 | — | 1.5 | 3.5 |
| Grand total | 27.5 | — | 11.5 | 39 |
Confidence: medium. Assumptions: (1) Task 3 lands on its preferred path (owner re-releases
pigeon-httpdropping the unusedddtracedep); if the decided removal fallback triggers instead, add ~1 BE day to portlib/wit_ai/app_v2.rb's 5 call sites tolib/http.rb(not in this total — see Task 3); (2) the enum conversion (Task 7) is mechanical, no hidden 7.2enumbehavior beyond syntax; (3) three open questions (sidekiq-pro registry access, Datadog profiling parity,webpackerboot-on-7.2) are not assumed resolved — an unfavorable answer could add rework; (4) Task 4'sgrape2.0→2.4 bump (+0.5 BE day) is assumed to be a clean, non-breaking bump per the RFC's own gem-waves table — Phase 2's gem wave explicitly depends on this having landed here.
Tasks (ordered by execution-plan dependency)
Task 1: [BE] Bundler version reconciliation
One consistent Bundler version across the lockfile, 3 Dockerfiles, and 6 CI lines.
Status: ✅ Actionable
Implementation Plan
| Action | File | What changes |
|---|---|---|
| edit | Gemfile.lock:942-943 | BUNDLED WITH → target version (e.g. 2.5.x) |
| edit | Dockerfile:32, Dockerfile.develop:6, Dockerfile.nonroot:42 | ENV BUNDLE_VERSION → target (keep nonroot's = form) |
| edit | bitbucket-pipelines.yml:26,50,67,296,344,359 | gem install bundler -v → target, all 6 occurrences |
Implementation steps
- Pick a target version; confirm
Gemfile.lockresolves under it locally. - Edit all 3 Dockerfiles + all 6 CI lines + the lockfile footer.
bundle installlocally, push, confirm CI green.
Acceptance criteria
- All 3 Dockerfiles, the lockfile, and all 6 CI lines agree on one Bundler version.
-
bundle checkpasses locally and in CI.
Test strategy: Green full CI run (build+lint+rspec); no app-level spec needed.
Effort estimate
| Discipline | Days |
|---|---|
| Backend/Infra | 0.5 |
| QA | 0 |
| Total | 0.5 |
Pure toolchain edit, no user behavior — QA 0.
Run to verify
bundle install && bundle check
Depends on: None. First task.
Task 2: [BE] acts_as_paranoid 0.9.0 → 0.10.3
Lifts the hard
activerecord < 7.2ceiling blocking the Rails 7.2 bump.
Status: ✅ Actionable
Implementation Plan
| Action | File | What changes |
|---|---|---|
| edit | Gemfile:109 | pin ~> 0.10.3 |
| n/a | Gemfile.lock:105-107 | ceiling relaxes to >= 6.1, < 8.1 |
Implementation steps
- Bump the pin,
bundle update acts_as_paranoid, confirm the ceiling relaxes. - Re-run every soft-delete model spec (
with_deleted,only_deleted,paranoia_destroy). - Check the 0.9.0→0.10.3 changelog for a renamed scope/config.
Acceptance criteria
-
acts_as_paranoid 0.10.3resolves with no AR/AS ceiling conflict. - All soft-delete model specs pass unchanged.
Test strategy: RSpec model specs asserting soft-delete/restore scopes unchanged.
Effort estimate
| Discipline | Days |
|---|---|
| Backend | 1 |
| QA | 0.5 |
| Total | 1.5 |
QA 0.5 — soft delete affects data visibility, not a pure refactor.
Run to verify
bundle exec rspec spec/models/ && bundle exec rubocop
Depends on: None. Unblocks Task 8 (Rails 7.2 bump — hard resolution blocker).
Task 3: [BE] ddtrace → datadog 2.x migration + google-protobuf unpin
Datadog APM/profiling keeps working on a gem line that actually runs on Ruby 3.4 (
ddtrace1.x caps at Ruby 3.3).
Status: ⚠️ Partially blocked — the migration itself is actionable now. Only pigeon.gemspec:38
(unconstrained, unused ddtrace dep) is externally gated. Preferred: owner re-releases
pigeon-http dropping it. Decided fallback (2026-07-13): remove pigeon-http, port its 5
lib/wit_ai/app_v2.rb call sites to lib/http.rb (Step 4 below).
Implementation Plan
| Action | File | What changes |
|---|---|---|
| edit | Gemfile:200 | ddtrace → gem 'datadog', '~> 2.37' |
| edit | Gemfile:204 | drop google-protobuf ~>3.19 pin once no longer needed |
| edit | config/initializers/datadog.rb | rewrite Datadog.configure to the 2.x namespaced API |
| edit (fallback only) | lib/wit_ai/app_v2.rb | swap PigeonHttp → Http at lines 22/31/36/42/47; append get_all's query params to the URL string (Http#call has no query: kwarg) |
Implementation steps
- Bump to
datadog ~> 2.37; rewriteconfig/initializers/datadog.rb's configure block. - Drop the
google-protobufpin; confirm nothing else needs it. - If the re-release lands: bump
pigeon-http, confirmddtraceis gone fromGemfile.lock. - If the fallback triggers: replace
PigeonHttpwithHttpinlib/wit_ai/app_v2.rb(5 call sites), write new specs (zero existing coverage today), deletelib/pigeon_http.rb. - Validate Datadog trace/profiling parity in staging.
Acceptance criteria
-
Gemfile.lockhas noddtrace, resolves cleanly. - Datadog traces/APM appear in staging with equivalent tags.
- (if fallback) All 5
WitAi::AppV2methods pass new specs againstHttp.
Test strategy: Boot spec for the Datadog initializer; (fallback only) new request specs for
WitAi::AppV2 against a stubbed Net::HTTP.
Effort estimate
| Discipline | Days |
|---|---|
| Backend | 3 |
| QA | 1 |
| Total | 4 |
Assumes the preferred path lands. If the fallback triggers, add ~1 BE day (not in the Effort Summary grand total — track separately).
Run to verify
bundle exec rspec spec/lib/wit_ai/ && bundle exec rubocop
Depends on: None to start. Blocks Task 12 (Ruby 3.4 bump).
Task 4: [BE] Wave-0 toolchain & dependency housekeeping (sidekiq, rack-cors, webdrivers, rubocop, grape)
Five small, independent wave-0 items land as mechanical Gemfile/config bumps before the framework moves.
Status: ✅ Actionable
Implementation Plan
| Action | File | What changes |
|---|---|---|
| edit | Gemfile:168 | sidekiq → ~> 7.3.10 (Ruby 3.4 forward-compat fixes; sidekiq-pro's <8 ceiling already satisfied) |
| edit | Gemfile:190 | rack-cors → ~> 2.0 (Rack-3-safe header casing) |
| edit/delete | Gemfile:32, Gemfile:38 | bump selenium-webdriver past 4.11; delete webdrivers (archived) |
| edit | Gemfile:83,85, .rubocop.yml:6 | rubocop → >= 1.88, drop rubocop-performance 1.9.2 ceiling, TargetRubyVersion → 3.4 |
| edit | Gemfile:180 | gem 'grape', '~> 2.0.0' → '~> 2.4' (Rack 3 / Ruby 3.4 currency); grape-swagger/grape-entity stay at their currently-resolved versions this wave — no compatibility issue flagged for them |
Implementation steps
- Bump
sidekiq; boot Sidekiq locally, re-runspec/workers/. - Bump
rack-cors; re-run CORS request specs, confirm header casing. - Remove
webdrivers, bumpselenium-webdriver; confirm system specs launch via Selenium Manager. - Bump
rubocop/drop the performance-gem ceiling, fix.rubocop.yml:6; triage the new-offense delta from raisingTargetRubyVersion(no blanket cop disables). - Bump
grapeto~> 2.4,bundle update grape, and run the API specs underspec/api/covering the Grape surfaces (frontend_service,internal_service, etc.) to confirm no route/serializer regression from the 2.0→2.4 jump.
Acceptance criteria
-
sidekiq 7.3.10+sidekiq-pro 7.2.1resolve together; worker specs pass. - CORS headers correctly cased;
webdriversremoved; system specs still launch a browser. -
rubocopparses Ruby 3.4 syntax;.rubocop.yml:6reads3.4; CI lint step green. -
grape 2.4.xresolves cleanly alongsidegrape-swagger/grape-entityunchanged; allspec/api/specs pass.
Test strategy: Worker specs + CORS request specs + a manual Selenium boot + the full spec/api/
suite (Grape route/serializer coverage) + bundle exec rubocop as its own gate.
Effort estimate
| Discipline | Days |
|---|---|
| Backend | 3 |
| QA | 0.5 |
| Total | 3.5 |
QA 0.5 covers only the CORS behavioral check; the other four (incl. grape) are mechanical version bumps validated by existing specs. +0.5 BE day added for the
grape2.0→2.4 bump (bundle update + running thespec/api/surface) versus the original four-item estimate.
Run to verify
bundle exec rspec spec/workers/ spec/requests/ spec/api/ && bundle exec rubocop
Depends on: None. Should land before Task 12 (rubocop must parse the new syntax at the Ruby
bump); grape 2.4.x is also assumed already landed by Phase 2's Task 2 (its grape line explicitly
depends on this bump having happened here).
Task 5: [BE] karafka 2.2 → 2.5 sequential chain + karafka-web → 0.11
The Kafka consumer runs on a supported Karafka line, stepped through each mandated minor.
Status: ✅ Actionable — must run as its own multi-step sub-sequence, not one combined bump.
Implementation Plan
| Action | File | What changes |
|---|---|---|
| edit | Gemfile:215 | karafka steps ~>2.2→~>2.3→~>2.4→~>2.5, one deploy each |
| edit | Gemfile:217 | karafka-web → ~> 0.11 (last, after the chain) |
| n/a | karafka.rb | re-check `setup do |
Implementation steps
- Bump to
~>2.3first (rebuildskarafka-rdkafka's native ext); boot the consumer, re-run specs. - Repeat 2.3→2.4→2.5 as separate deploys, per Karafka's own per-minor upgrade docs.
- Bump
karafka-webto0.11last; confirm the dashboard boots against the final Karafka version.
Acceptance criteria
- Each of the 3 minor steps lands as its own deploy with a clean consumer boot.
-
karafka-web0.11 dashboard loads;chatbot_incoming_messagetopic consumes correctly.
Test strategy: Manual consumer boot smoke per step (no existing CI coverage — Task 6 starts
closing that gap) + any consumer specs under spec/.
Effort estimate
| Discipline | Days |
|---|---|
| Backend | 3 |
| QA | 1 |
| Total | 4 |
Assumes no librdkafka ABI issue surfaces mid-chain (would add time if one does).
Run to verify
bundle exec karafka console
Depends on: None. Should land before Task 6's Karafka boot-smoke CI gate targets the final (2.5.x) consumer.
Task 6: [BE/Infra] CI gates before the framework bump
The pipeline catches an asset-compile break, a Karafka boot failure, or a per-process boot regression before the Rails/Ruby bump PRs land — today it catches none of these.
Status: ✅ Actionable
Implementation Plan
| Action | File | What changes |
|---|---|---|
| edit | bitbucket-pipelines.yml | new step: webpacker:compile && assets:precompile |
| edit | bitbucket-pipelines.yml | new step: Karafka boot smoke against a test topic |
| edit | bitbucket-pipelines.yml | new step(s): boot Puma/Sidekiq/console + assert the config/application.rb:41-79 Vault-unreachable fail-open path doesn't crash any process |
Implementation steps
- Add the asset-precompile step; sanity-check it fails on a deliberately-broken webpacker config.
- Add a Karafka boot-smoke step (process starts, exits 0, against a disposable test topic).
- Add per-process boot checks (Puma, Sidekiq, console,
sidekiq-cronschedule load) plus a Vault-unreachable assertion (point Vault config at an unreachable host, confirm fail-open boot). - Document whether
update_automation_result.rb's bare-rubyCI invocation loads the Rails env (currently unverified).
Acceptance criteria
- CI fails when asset compilation is deliberately broken (gate is real).
- CI boots the Karafka consumer + every process type and asserts the Vault-unreachable path doesn't crash any of them.
-
update_automation_result.rb's Rails-env-loading status is documented.
Test strategy: Pipeline steps verified red→green by deliberately breaking then fixing each gate.
Effort estimate
| Discipline | Days |
|---|---|
| Backend/Infra | 2 |
| QA | 0.5 |
| Total | 2.5 |
Assumes Bitbucket Pipelines' existing service containers (Kafka/Redis/Postgres) suffice.
Run to verify
git push # inspect the new bitbucket-pipelines.yml steps
Depends on: None. Should land before Task 8 (Rails 7.2 bump PR).
Task 7: [BE] Enum-syntax modernization — 46 declarations
Every
app/models/enumuses the Rails 7.0+ positional syntax, checked against the Rails 7.2 changelog before the framework bump.
Status: ✅ Actionable
Implementation Plan
| Action | File | What changes |
|---|---|---|
| edit | app/models/intent.rb:73-76 | 4 declarations → positional form |
| edit | app/models/path.rb:58,63, app/models/response.rb:53-55, app/models/attachment.rb:52-53 | 7 more declarations |
| edit | remaining app/models/*.rb | remaining ~35 (full list: grep -rnE '^\s*enum\b' app/models/) |
Implementation steps
- Run the grep for the full current 46-line list.
- Convert each to
enum :column, {...}, suffix:, prefix:positional form, one file at a time, running that model's spec after each. - Cross-check the Rails 7.2
enumchangelog for any semantic (not just syntax) change.
Acceptance criteria
- Zero remaining legacy hash-argument
enumdeclarations. - All model specs pass unchanged (values/scopes/predicates identical).
Test strategy: RSpec model specs asserting enum scopes/predicates resolve to the same values.
Effort estimate
| Discipline | Days |
|---|---|
| Backend | 3 |
| QA | 1 |
| Total | 4 |
QA 1 — a missed case fails silently as a wrong enum mapping (data-correctness risk), not a boot error; warrants a manual spot-check pass.
Run to verify
bundle exec rspec spec/models/ && bundle exec rubocop app/models/
Depends on: None. Should land before Task 8 (re-verified against the 7.2 changelog at bump time).
Task 8: [BE] Rails 7.2.3.1 bump PR + app:update review
The app boots on Rails 7.2.3.1 with
load_defaultsstill at6.1— isolating "does it boot" from "does it behave under new defaults."
Status: ✅ Actionable, gated on Tasks 2, 6, 7.
Implementation Plan
| Action | File | What changes |
|---|---|---|
| edit | Gemfile:9 | ~> 7.1.3.2 → ~> 7.2.3.1 |
| n/a | config/application.rb:13 | unchanged this task — stays 6.1 |
| n/a (generated) | — | review every bin/rails app:update diff explicitly |
Implementation steps
- Bump
Gemfile:9,bundle update rails(requires Task 2 landed first). - Run
bin/rails app:update; review every generated/changed file — no blanket-accept. - Boot every process type with
load_defaultsunchanged; re-verify the 46 enums; run full rspec.
Acceptance criteria
- App boots on 7.2.3.1 with
load_defaultsunchanged at6.1. - Every
app:updatediff explicitly reviewed (accepted or rejected, not blanket). - Full rspec suite green.
Test strategy: Full existing rspec suite + a manual boot smoke per process type.
Effort estimate
| Discipline | Days |
|---|---|
| Backend | 2 |
| QA | 1 |
| Total | 3 |
Framework bumps carry regression risk even with defaults unchanged, hence QA 1.
Run to verify
bundle exec rspec && bin/rails app:update --dry-run
Depends on: Task 2, Task 6, Task 7. Unblocks Tasks 9–11.
Task 9: [BE] load_defaults step 1 — Rails 7.0 defaults
The app runs under the full Rails 7.0 default set (not the partial hand-applied hybrid it has today), as its own revertible deploy.
Status: ✅ Actionable
Implementation Plan
| Action | File | What changes |
|---|---|---|
| edit | config/initializers/new_framework_defaults_7_0.rb | uncomment the remaining 6 of 19 settings |
| edit | config/application.rb:13 | 6.1 → 7.0 |
Implementation steps
- Uncomment the remaining settings; deploy with
load_defaultsstill6.1to check the file loads. - Flip to
7.0; deploy; monitor the SHA-256 key generator (encrypted cookies/message verifiers) andcache_format_versionmoving to the 7.0 format through the rolling-deploy window.
Acceptance criteria
- All 19 settings active;
load_defaults 7.0deploys cleanly, no cookie/session error spike. - Full rspec suite green at this defaults level.
Test strategy: Full rspec suite + a staging soak watching cookie/session error rates during the rolling deploy (mixed-version pods).
Effort estimate
| Discipline | Days |
|---|---|
| Backend | 1.5 |
| QA | 1 |
| Total | 2.5 |
cookies_serializeris already:json— lower risk than a fresh app.
Run to verify
bundle exec rspec
Depends on: Task 8. Blocks Task 10.
Task 10: [BE] load_defaults step 2 — Rails 7.1 defaults
The app runs under the full Rails 7.1 default set — currently 100% dead/commented documentation.
Status: ✅ Actionable
Implementation Plan
| Action | File | What changes |
|---|---|---|
| edit | config/initializers/new_framework_defaults_7_1.rb | uncomment all 284 lines' settings |
| edit | config/application.rb:13 | 7.0 → 7.1 |
Implementation steps
- Uncomment all settings; confirm
active_record.encryption.hash_digest_classis a no-op (no Rails-nativeencryptsusage anywhere — encryption here is vialockbox). - Flip to
7.1; deploy; monitorto_time_preserves_timezone, the message-serializer default move, and the cache serializer default through the rollout.
Acceptance criteria
- All settings active;
load_defaults 7.1deploys cleanly. - Full rspec suite green at this defaults level.
Test strategy: Full rspec suite + staging soak watching for to_time/serializer regressions.
Effort estimate
| Discipline | Days |
|---|---|
| Backend | 1.5 |
| QA | 1 |
| Total | 2.5 |
hash_digest_classno-op already grep-confirmed.
Run to verify
bundle exec rspec
Depends on: Task 9. Blocks Task 11.
Task 11: [BE] load_defaults step 3 — Rails 7.2 defaults (generated)
The app runs under the full Rails 7.2 default set — the final defaults-stepping deploy, matching Phase 2's stated starting point.
Status: ✅ Actionable
Implementation Plan
| Action | File | What changes |
|---|---|---|
| create (generated) | config/initializers/new_framework_defaults_7_2.rb | via bin/rails app:update |
| edit | config/application.rb:13 | 7.1 → 7.2 |
Implementation steps
- Generate the file via
app:update; review its settings (not exhaustively published upstream). - Uncomment, deploy with defaults still
7.1to check load, then flip to7.2; deploy.
Acceptance criteria
- File generated, reviewed, fully active;
load_defaults 7.2deploys cleanly. - Full rspec suite green — app now matches Phase 2's starting point.
Test strategy: Full rspec suite + the same rolling-deploy monitoring as Tasks 9–10.
Effort estimate
| Discipline | Days |
|---|---|
| Backend | 1.5 |
| QA | 1 |
| Total | 2.5 |
Assumes no surprise breaking default in the generated file.
Run to verify
bundle exec rspec
Depends on: Task 10.
Task 12: [BE/Infra] Ruby 3.2.2 → 3.4.10 bump PR
The app runs on Ruby 3.4.10 across every pin — including fixing
Dockerfile.develop's pre-existing stale-minor defect.
Status: ✅ Actionable, gated on Task 3 (ddtrace→datadog complete) and Task 4 (lint toolchain
ready).
Implementation Plan
| Action | File | What changes |
|---|---|---|
| edit | .ruby-version:1, Gemfile:6 | 3.2.2 → 3.4.10 |
| edit | Dockerfile:1-2,21, Dockerfile.nonroot:1-2,23,42 | ruby:3.2.2-alpine → ruby:3.4.10-alpine |
| edit | Dockerfile.develop:1,52 | ruby:3.1.0-alpine → ruby:3.4.10-alpine — fixes the pre-existing defect, not just re-pins it |
| edit | bitbucket-pipelines.yml:1 | ruby:3.2.2 → ruby:3.4.10 |
Implementation steps
- Confirm Task 3 has fully landed (no
ddtraceinGemfile.lock). - Bump
.ruby-version/Gemfile:6;bundle installunder Ruby 3.4.10 locally. - Re-run the bundled-gem
requiregrep (base64/csv/mutex_m/drb/observer/getoptlong/rinda/syslog/abbrev) as a post-bump check — confirm noLoadError. - Bump all 3 Dockerfiles + CI image in the same PR; re-pin
debase-ruby_core_sourceif still needed.
Acceptance criteria
- All 7 Ruby-version locations agree on
3.4.10, includingDockerfile.develop. - Post-bump bundled-gem grep shows zero
LoadErrorrisk; full rspec + boot checks green.
Test strategy: Full rspec suite under Ruby 3.4.10 + Task 6's new CI gates, exercised for the first time against the new Ruby version.
Effort estimate
| Discipline | Days |
|---|---|
| Backend/Infra | 2 |
| QA | 1 |
| Total | 3 |
Assumes no native-ext gem (
pg/nokogiri/ffi/msgpack/sassc/karafka-rdkafka) fails to compile against Ruby 3.4's ABI.
Run to verify
bundle exec rspec && bundle exec rubocop
Depends on: Task 3, Task 4, Task 11 (per the RFC's own execution-plan order — Ruby moves after defaults settle, not alongside).
Task 13: [BE] aegis Rails-7.2/Ruby-3.4 boot spike (with detach fallback)
Confirms the source-audited "compatible-as-is" assessment on a real running branch, with a decided fallback if either residual check fails.
Status: ✅ Actionable as a spike (source-audited compatible-as-is, medium-high confidence). This is a decision gate, not a code change: if either check fails, the decided fallback (2026-07-13) is to temporarily detach aegis.
Implementation Plan
| Action | File | What changes |
|---|---|---|
| verify | config/initializers/aegis.rb:21 | Aegis.replace_rails_logger! boots; #tagged stays a no-op (not a raise) against config.log_tags = [:request_id] |
| verify | config/initializers/sidekiq.rb:34-37,50-53 | client/server middleware chain still wraps jobs |
| edit (fallback only) | config/initializers/aegis.rb, config/initializers/sidekiq.rb, config/application.rb:17 | comment out aegis requires/wiring |
Implementation steps
- Boot the app on Ruby 3.4/Rails 7.2; confirm
Rails.loggerisAegis::Logger, no crash. - Send a tagged request; confirm the tag is silently absent (expected today) — not an exception.
- Boot Sidekiq, enqueue a test job, confirm both patch classes fire without error.
- If either check fails: comment out aegis's requires/wiring — but first confirm no other log path
(
lograge, Rails' default logger,filter_parameter_logging.rb) is relied on for PII masking, since that's a data-protection control, not pure observability.
Acceptance criteria
- App boots with
Aegis::Loggeractive, no crash;#taggedbehavior observed and documented. - Sidekiq middleware chain confirmed still wrapping jobs.
- (if fallback) No other log path relied on for PII masking before detaching.
Test strategy: Manual boot + request/job smoke on the real upgrade branch — not settleable by static reading alone.
Effort estimate
| Discipline | Days |
|---|---|
| Backend | 1.5 |
| QA | 0.5 |
| Total | 2 |
Assumes the spike passes on the attached path; the detach itself is a small config change if not.
Run to verify
bundle exec rails runner "Rails.logger.tagged('req-1') { Rails.logger.info('test') }"
Depends on: Task 12. Feeds Task 14.
Task 14: [BE/Infra] Staging bake + dual-cloud canary rollout
Phase 1 is validated end-to-end and rolled out gradually across both deployment targets, closing the phase.
Status: ✅ Actionable, gated on all prior tasks.
Implementation Plan
| Action | File | What changes |
|---|---|---|
| n/a | deploy/, deploy-alicloud/ | staged canary, one target first, then the other — not simultaneous |
Implementation steps
- Run the full rspec suite on the complete Phase 1 branch (Ruby 3.4.10 + Rails 7.2.3.1 +
load_defaults 7.2); boot-smoke every process type. - Staging soak: Karafka consumer on real traffic,
sidekiq-cronschedules (config/initializers/sidekiq.rb:46), Datadog traces, asset-compile parity, the Vault-unreachable assertion (Task 6), and Task 13's aegis check. - Canary one deployment target, monitor, then the second; confirm each prior task's change is independently revertible (no accidental squash into one irreversible deploy).
Acceptance criteria
- Full rspec suite green on the complete branch; every process type boots cleanly.
- Staging soak passes on all items in Step 2; canary succeeds on both AWS and Alicloud, staged.
Test strategy: Full rspec suite + manual staging soak checklist + a staged production canary with monitoring before full rollout.
Effort estimate
| Discipline | Days |
|---|---|
| Backend/Infra | 2 |
| QA | 1.5 |
| Total | 3.5 |
QA weighted above the standard 25% guidance — this is the single point where all 14 tasks' outputs are validated together for the first time.
Run to verify
bundle exec rspec && bundle exec karafka console
Depends on: All prior tasks (1–13).
Ordering rationale
- Critical path: Task 3 (
ddtrace→datadog) gates Task 12 (Ruby 3.4 bump) —ddtrace1.x cannot run on Ruby 3.4. Push thepigeon-httpowner's re-release in parallel with the rest of wave 0 so the preferred path is ready when Task 12 needs it. - Task 2 (
acts_as_paranoid) gates Task 8 (Rails 7.2 bump) — the hardest wave-0 blocker. - Tasks 6 (CI gates) and 7 (enum modernization) land before Task 8 so the bump PR has both a safety net and a clean enum surface to verify against the 7.2 changelog at bump time.
- Tasks 9→10→11 (
load_defaultsstepping) are strictly sequential, each its own deploy — never compressed into one PR (cache-format/cookie-serializer changes are only cleanly revertible per-step). - Task 12 (Ruby bump) is deliberately last framework-adjacent change, matching the RFC's own numbered execution plan (Ruby moves after Rails settles, not alongside).
- Tasks 4 and 5 are independent wave-0 items, parallelizable across engineers.
- Task 13 (aegis spike) can run any time once Ruby 3.4 boots (Task 12) — a verification spike, not a code-dependency gate — but should close before Task 14's staging bake.
Skipped / blocked items
No tasks were fully blocked or excluded — every RFC execution-plan step is covered by an actionable or partially-blocked task above (Task 3 and Task 13 are the two ⚠️/decision-gated items, both included inline with their fallback paths). Track-level scope exclusion (Ruby 4.0.5) applies only in the Phase 2 breakdown.
Residual open questions not gating any task above — tracked via the RFC's own Open Questions
section, not repeated here as tasks: OQ-4 (does sidekiq-pro registry access cover 7.3.10?),
OQ-5 (Datadog profiling parity post-migration), OQ-6 (does webpacker actually boot/compile
under Rails 7.2? — answered by Task 6's new CI gate, not assumed).