v4.4 — Keystone Findings Remediation
Fixed the v4.3 Keystone Architecture Sweep's 18-item fix-before-ship shortlist — unwrapped multi-write transactions, duplicated business rules, and architecture-registry blind spots — including three live, currently-shipped regressions that were silently corrupting data.
Summary
The v4.3 Keystone Architecture Sweep (9 audit passes across 14 registry domains, PR #1402) was report-only — it found problems but changed no app code. v4.4 is the fix. Its source is the sweep's consolidated triage index, §6 "fix-before-ship shortlist": 18 numbered items. Two were already ticketed and shipped standalone before this milestone kicked off (OBJ-1276, OBJ-1280, OBJ-1282); this milestone's scope was the remaining 16, tracked as 14 Multica sub-issues (OBJ-1287–OBJ-1300) plus two follow-ons spawned mid-milestone (OBJ-1319, OBJ-1321) and one new finding caught during review (OBJ-1332).
Unlike a standard GSD milestone, this one was driven entirely through Multica crew sub-issues rather than GSD phases — Orion scoped 16 tickets into two logical stages (code + decisions in parallel, then docs), Codi implemented each fix against its existing sibling pattern in the same file or directory ("mirror the sibling, don't invent"), Roy reviewed, and each PR shipped independently straight to master on the beta weekly-release-train policy. Three of the fixes were live production bugs actively producing bad data: StreakRepairOfferResolver wrote duplicate gamification records on every eligible dashboard read, EnqueueClear's job_id mismatch made the admin demo-data-clear progress indicator always read idle, and FinalizeCommunityChallenge's unwrapped transaction meant a daily cron retry could re-send "badge earned" notifications indefinitely after any partial failure.
All 16 real sub-issues plus the mid-milestone follow-ons are done. Combined with the three findings ticketed standalone outside this epic (OBJ-1276, OBJ-1281, OBJ-1283 — all independently shipped), the entire 18-item v4.3 §6 shortlist is now closed.
Goal
Ship the real code + doc fixes for the v4.3 Keystone Architecture Sweep's §6 fix-before-ship shortlist. Unlike v4.3 (report-only, no app code touched), v4.4 changes
rails_api/app/**,scripts/, anddocs/architecture/**. Close out the audit's highest-confidence, lowest-cost defects — several of which are live, currently-shipped regressions accumulating bad data.—
.planning/milestones/v4.4-keystone-findings-remediation-ROADMAP.md
Scope — What Shipped
- Atomicity fixes (6) — wrapped previously-unwrapped multi-write sequences in
ActiveRecord::Base.transaction, each with a forced-mid-sequence-failure spec proving rollback:GoalTracking::CheckInHabit,AiWorkforce::ProcessWebhookResult,Social::FinalizeCommunityChallenge,Ai::CoachService#persist_turn_safe,ContentModeration::ReviewContentFlag,CriticalPath::RecordPlay/AwardStimXp. - Idempotency / re-entrancy guards (2) —
Gamification::StreakRepairOfferResolverno longer creates duplicateUserAction/badge-feed writes on repeated reads;Social::FinalizeCommunityChallenge's push notifications are idempotent across transaction retries (caught mid-milestone during code review, OBJ-1332). - Boundary fix (1) —
AiWorkforce::PauseAiEmployee's GraphQL resolver no longer writes toAiEmployeedirectly; the write moved into a newAiWorkforce::PauseEmployee < Interaction::Base. - Deduplicated business rules (1 batched ticket, 3 rules) —
Gamification::RepairStreak's XP debit now routes through the canonicalGamificationService.award_xpsingle-writer path; the AI check-in streak-milestone tier list was unified;AcceptPartnerRequest/AcceptAllyInvite's duplicatedaward_xp_to_both!logic moved to a newSocial::PartnershipActivationservice; the "fail closed ifSettings.aimissing" guard — reimplemented 6 times — consolidated intoAi::SettingsGuard.enabled?. - Live-data regression fix (1) —
DemoData::EnqueueClear'sjob_idnow matches the keyClearDemoDataJobrecords progress under. - Decisions resolved (2) —
promotion_criteriavs.promotion_requirements_metdivergence onAiEmployee(kept the model's real validation, removed unenforced tenure gates from the GraphQL type); schedule-vs-retireGenerateAiCheckInPromptsJob(Josh chose schedule, ramped behindAI_CHECK_IN_INTERNAL_ALLOWLIST). - Registry fix (1) —
scripts/arch-domains.mjsaudited and corrected registry-wide socapture-arch-evidence.mjsreports accurate glob matches for every previously-blind domain. - Follow-on: Crono scheduling (1) —
AiCoaching::GenerateAiCheckInPromptsJob, fully built but never scheduled, now runs daily at 10:00 viarails_api/config/cronotab.rb, gated behind the internal allowlist ramp — the job that fires all 5 already-shipped frontend check-in-prompt surfaces for the first time. - Docs (2 passes) — a consolidated Keystone docs-drift pass (11-item punch list:
acts_as_paranoidmodel list, bounded-context counts, missing Teams section, contradictory GDPR sections, domain-events subscribers,arch-gates.mdexception lists) and a follow-up reconciling the AI check-in scheduling docs once OBJ-1319/OBJ-1297 landed.
Fixes shipped (16 tickets, 2 stages)
| Ticket | Finding | PR(s) | Highlights |
|---|---|---|---|
| OBJ-1287 | goal_tracking: CheckInHabit transaction wrap (§6-2) | #1408 | Mirrors 3 sibling interactions in the same dir |
| OBJ-1288 | gamification: StreakRepairOfferResolver idempotency (§6-3) | #1407 | Live data-corruption fix — stops duplicate writes on repeated dashboard reads |
| OBJ-1289 | ai_workforce: ProcessWebhookResult atomicity (§6-4) | #1411 | Terminal status commits last — mid-sequence failure stays retryable |
| OBJ-1290 | social: FinalizeCommunityChallenge transaction + re-processing guard (§6-6) | #1412 · #1432 | Worst blast-radius fix in the sweep — unconditional daily cron; 3 CI-infra-blocked rounds, see Key Decisions |
| OBJ-1291 | ai_coaching: persist_turn_safe transaction wrap (§6-7) | #1414 | No orphaned unanswered "user" turn on mid-sequence failure |
| OBJ-1292 | content_moderation: ReviewContentFlag transaction + re-entrancy guard (§6-8) | #1416 | Mirrors sibling bulk_review_content_flags.rb |
| OBJ-1293 | critical_path: RecordPlay/AwardStimXp atomicity (§6-9) | #1415 | Retry now recovers a crashed Stim XP award instead of losing it |
| OBJ-1294 | platform: EnqueueClear job_id mismatch (§6-1) | #1417 | Live regression — admin progress indicator was always stuck on idle |
| OBJ-1295 | ai_workforce: PauseAiEmployee → extract interaction (§6-12) | #1422 · #1424 | Closes the last resolver-writes-directly boundary violation in AI Workforce |
| OBJ-1296 | g27 batch: RepairStreak XP bypass + AI check-in tier list + award_xp_to_both + Settings.ai guard ×6 (§6-14a/b/c) | #1421 · #1426 | 3 unrelated dedup findings bundled — all 218 existing specs on touched call sites passed unchanged |
| OBJ-1297 | DECISION: schedule-or-retire GenerateAiCheckInPromptsJob (§6-10) | — | Josh: schedule + internal-allowlist ramp; spawned OBJ-1319 |
| OBJ-1298 | DECISION+fix: promotion_criteria vs promotion_requirements_met divergence (§6-13) | #1418 | Roy reversed his own first ruling after finding it would have disabled autonomous promote for every employee — see Key Decisions |
| OBJ-1299 | tooling: arch-domains.mjs registry-wide audit-and-fix (§6-17) | #1420 | Coordinated with standalone OBJ-1283 (teams glob) rather than duplicating it |
| OBJ-1300 | docs: consolidated Keystone docs-drift pass (§6-15/16/18 + §9) | #1410 | All 11 §9 punch-list items closed; ran before all code landed (see Key Decisions) |
| OBJ-1319 | ai_coaching: schedule GenerateAiCheckInPromptsJob via Crono + allowlist ramp (follow-on to OBJ-1297) | #1419 | 5 already-shipped frontend surfaces go live for the first time |
| OBJ-1321 | ai_coaching: reconcile check-in-prompt scheduling docs (follow-on to OBJ-1319) | #1437 | Replaced OBJ-1300's deliberately-neutral "not scheduled" wording with the shipped outcome |
| OBJ-1332 | social: FinalizeCommunityChallenge push notifications can duplicate on transaction retry (new finding, child of OBJ-1290) | #1438 | Roy caught this reviewing OBJ-1290 — the idempotency fix's own retry path could re-send notifications |
Every fix mirrors an established sibling pattern already in the same file or directory rather than inventing a new one (e.g. EnqueueReseed for OBJ-1294, WelcomeBackOfferResolver for OBJ-1288, BulkReviewContentFlags for OBJ-1292) — the fidelity contract set at kickoff.
Key Decisions
- Schedule, not retire,
GenerateAiCheckInPromptsJob— Josh confirmed Option A (schedule) over retiring the fully-built, never-scheduled job, and specifically chose to add anAI_CHECK_IN_INTERNAL_ALLOWLISTramp before opening it to all users, over shipping immediately — a notification-fatigue/cost guard. Codi had already implemented and pushed the scheduling fix off a duplicate decision ticket created during the kickoff race (see below), skipping the ramp; that branch was held until the ramp was added. promotion_confirmed_atgate rejected, twice-ruled — Roy independently ruled on whetherAiEmployee's promotion flow needed apromotion_confirmed_atcheck twice, on two different tickets created by a duplicate-skeleton race, and reached opposite conclusions. The second, more thorough ruling won: do not add the check —promotion_confirmed_atis only ever set during the same mutation call that performs the promotion, so gating on it up front would have permanently disabled the "Promote to Autonomous" button for every AI employee. The fix (OBJ-1298 / PR #1418) instead removed the GraphQL type's own inventedweeks_activetenure gates, which disagreed with the model's realpromotion_requirements_metvalidation.- Docs ran ahead of code, deliberately worded around it — OBJ-1300's docs pass (PR #1410) merged while 5 code tickets were still open. It caused no damage: 10 of its 11 punch-list items were pre-existing doc drift unrelated to the atomicity fixes; the one item that touched an open decision (
GenerateAiCheckInPromptsJob's schedule) was worded as "not yet scheduled" rather than asserting an outcome, specifically to avoid pre-empting OBJ-1297. OBJ-1321 later reconciled that wording once the decision shipped. FinalizeCommunityChallengeblocked 3 rounds on CI infra, not code —Playwright Smoke Tests (Preview)was cancelled three times by thesmoke-preview-lanerepo-wide concurrency group under heavy PR volume (the same root cause as the already round-capped OBJ-1237). Riley escalated directly to a human rather than re-asking Tess for an answer already capped at 3 rounds; the fix itself was clean throughout and eventually merged via PR #1412 + a follow-up CHANGELOG PR #1432.- Duplicate sub-issue skeleton at kickoff — a parallel process created the canonical 14-ticket skeleton (OBJ-1287–1300) the same minute Orion, working from an empty
issue childrencheck, built a second 16-ticket skeleton (OBJ-1303–1318) on itsepic: truehandback. Both had dispatched Codi on the same fixes. Orion cancelled its own 16 duplicates once the collision was found; the canonical set is what shipped. - A cancelled duplicate skeleton produced a false "16/16 complete" stage signal — because the cancelled duplicates were the only staged children, and
cancelledis a terminal status, the Multica stage barrier reported "Stage 1: 16/16 done" while the real, unstaged canonical work was still roughly half in flight. Orion caught it before the epic closed early. The canonical set was deliberately never retrofitted with stages afterward (tickets were already scattered across every status; retrofitting would have gated nothing).
Requirements Coverage
This milestone was driven entirely through Multica crew sub-issues rather than GSD phases, so there is no v4.4-keystone-findings-remediation-REQUIREMENTS.md or formal /gsd-audit-milestone pass — the same pattern as the v3.11 and v2.4 batches (see Project History). Coverage is tracked directly against the ROADMAP's Definition of Done and the source findings doc's §6 shortlist:
| Category | Count | Status |
|---|---|---|
| ATOMICITY-* (unwrapped multi-write → transaction) | 6 | All satisfied |
| IDEMPOTENCY-* (unguarded repeat side effect) | 2 | All satisfied |
| BOUNDARY-* (resolver writing outside the interaction layer) | 1 | Satisfied |
| DEDUP-* (duplicated business rule → shared home) | 3 (batched, 1 ticket) | All satisfied |
REGISTRY-* (arch-domains.mjs glob correctness) | 1 | Satisfied |
| DECISION-* (product/architecture call) | 2 | Both resolved, fixes landed |
| DOCS-* (architecture doc-drift reconciliation) | 2 passes | Satisfied |
16 / 16 real sub-issues done (OBJ-1287–1300, OBJ-1319, OBJ-1321) plus the mid-milestone OBJ-1332 finding, also done. The 16 cancelled tickets (OBJ-1303–1318) are duplicate-skeleton artifacts from the kickoff race, not open work. Combined with the three findings held out as standalone tickets — OBJ-1276, OBJ-1281, OBJ-1283 — which have all also shipped independently, the full 18-item v4.3 §6 fix-before-ship shortlist is closed.
Outcomes
- Three live data-integrity bugs are stopped:
StreakRepairOfferResolverno longer writes duplicate gamification records on repeated dashboard reads,EnqueueClear's admin progress indicator reflects real job state, andFinalizeCommunityChallenge's daily cron can no longer re-send duplicate "badge earned" notifications after a partial failure (and its own retry path can no longer duplicate push notifications either, per OBJ-1332). - Six previously-unwrapped multi-write sequences across goal tracking, AI Workforce, AI coaching, content moderation, and Critical Path are now atomic, each with a spec proving rollback on mid-sequence failure.
- Three duplicated business rules (gamification XP writes, AI check-in tier lists, partner/ally XP-award logic, and a 6x-reimplemented AI settings guard) now have one shared home each.
AiWorkforce::PauseAiEmployeeno longer writes outside the Interaction layer — closes the last known resolver-boundary violation flagged by the sweep.scripts/arch-domains.mjs's registry is accurate for every domain the sweep flagged as blind, socapture-arch-evidence.mjsreports real coverage going forward.- The AI check-in prompt job is live (internal allowlist only, for now) — 5 frontend surfaces that were dark since their own shipping milestone now function.
- Architecture docs (
docs/architecture/ddd/*,arch-gates.md) reflect the current, post-fix state of the codebase, not the v4.3-era drift the sweep found.
Tech Debt
- (carried forward, not silently dropped) Live-data cleanup — the OBJ-1288 and OBJ-1290 fixes stop future corruption but do not clean up already-written duplicate
UserAction/badge-feed rows or already-sent duplicate notifications from before the fixes landed. No cleanup was performed. Needs its own ticket if volume warrants — Orion's call, not mechanically ticketed here. - (carried forward) CI smoke-lane concurrency (
smoke-preview-lane, OBJ-1237 / OBJ-1290) — the contention that blocked OBJ-1290 three times is a standing infra problem, human-escalated, not fixed by this milestone. - (docs gap, this pass) Not every fix ticket has its own
CHANGELOG.mdentry — the highest-drama fixes (OBJ-1290, OBJ-1295, OBJ-1296, OBJ-1298, OBJ-1319/1321) do; the six smaller atomicity/idempotency fixes (OBJ-1287, 1289, 1291–1294, 1299, 1332) landed without one. All are internal correctness fixes with no user-visible behavior change, which is why this wasn't a blocker, but a future pass could backfill them for full engineering-log fidelity. - (process, filed for future milestones) Create the sub-issue skeleton exactly once, and re-run
issue childrenimmediately before creating it — the duplicate-skeleton race above cost real double-dispatch time. Stage the real ticket set at creation time if a docs-after-code ordering needs to be enforced; retrofitting later gates nothing. Never trust a stage-completion signal without checking actual child statuses — acancelledset of duplicates falsely reported 16/16 done here.
Related Artifacts
- Roadmap: v4.4-keystone-findings-remediation-ROADMAP.md
- Source findings doc: v4.3-keystone-arch-sweep-AUDIT-FINDINGS.md (PR #1402), §6 fix-before-ship shortlist
- Predecessor milestone: v4.3 Keystone Architecture Sweep (report-only, 9 audit passes, 14 registry domains) — no standalone
docs/milestones/narrative page; see the source findings doc linked above - Coordinating epic: OBJ-1286
- Excluded-but-related tickets (all shipped): OBJ-1276 (PR #1392), OBJ-1277 (PR #1391), OBJ-1280, OBJ-1281 (PR #1398), OBJ-1282 (PR #1400), OBJ-1283 (PR #1399)
Related Commits
5c94b0fe9— fix(goal_tracking): wrap CheckInHabit's write sequence in a transaction (OBJ-1287) (#1408)7dbd0d59a— fix(gamification): guard StreakRepairOfferResolver against duplicate offer writes (OBJ-1288) (#1407)4cf4c9f7a— fix(ai_workforce): wrap ProcessWebhookResult writes in a transaction (g32) (#1411)98a20ae85— fix(social): wrap FinalizeCommunityChallenge participant loop in transaction + idempotency guard (OBJ-1290) (#1412)f2846eda2— fix(ai_coaching): wrap CoachService#persist_turn_safe writes in a transaction (OBJ-1291) (#1414)f82bd767e— fix(content_moderation): wrap ReviewContentFlag in transaction with re-entrancy guard (#1416)0efc97004— fix(critical-path): always attempt Stim XP award so retries recover from a crashed award (OBJ-1293) (#1415)56ff63778— fix(demo-data): mirror EnqueueReseed job_id pattern in EnqueueClear (OBJ-1294) (#1417)43191b260— fix(ai_workforce): extract PauseAiEmployee resolver writes into an interaction (#1422)5a3d0cd2b— fix(gamification): route RepairStreak XP debit through GamificationService (OBJ-1296) (#1421)ee57f29d8— fix(ai_workforce): remove unenforced weeks_active gate from promotion_criteria (#1418)221b63d46— fix(tooling): registry-wide audit-and-fix pass for scripts/arch-domains.mjs (OBJ-1299) (#1420)db0556fca— docs: v4.3 Keystone Sweep §9 doc-drift roll-up (#1410)29875e165— feat(ai_coaching): schedule GenerateAiCheckInPromptsJob via Crono (OBJ-1319) (#1419)31661082b— docs(ai_coaching): reconcile check-in-prompt scheduling docs to shipped 10:00 + allowlist ramp (OBJ-1321) (#1437)9a06b42d7— fix(social): make FinalizeCommunityChallenge push notifications idempotent across transaction retries (OBJ-1332) (#1438)
Last updated: 2026-07-11