Skip to content

chore(flags): retire phoenix-funnel-readout flag — rip FlagGate + fallback + IaC declaration (ADR 0136)#3699

Merged
usirin merged 1 commit into
mainfrom
umut/3665-retire-funnel-readout-flag
Jul 20, 2026
Merged

chore(flags): retire phoenix-funnel-readout flag — rip FlagGate + fallback + IaC declaration (ADR 0136)#3699
usirin merged 1 commit into
mainfrom
umut/3665-retire-funnel-readout-flag

Conversation

@usirin

@usirin usirin commented Jul 19, 2026

Copy link
Copy Markdown
Member

Retires the released phoenix-funnel-readout flag (conversion-funnel readout, #1589), which serves on@100% in prod. Per ADR 0136 (machine nominates, human confirms — the flag-retirement label carries the confirmed graduate verdict), rips the gate + dead fallback so only the live served-on path remains, and deletes the flag's IaC declaration.

What changed

Rip the gate + dead fallback (behavior = byte-for-byte the served-on path)

  • FunnelPage.tsx: drop the useFlag(PHOENIX_FUNNEL_READOUT) gate, the flag-loading placeholder, and the flag-off 404 fallback — the surface always renders. Access stays server-authoritative: the ViewFunnel-gated funnel.summary read denies a non-mod the invisible UNAUTHORIZED, caught by the page's <Screen> (the "yetkin yok" state).
  • funnel/queries.ts: drop the readoutOn flag read + the Denied fallback branch; the requireFunnelAccess capability gate is now the sole gate.

Delete the IaC declaration

  • flagship/resources.ts: FUNNEL_READOUT_FLAG config + funnelReadoutFlag factory + import + re-export.
  • flags/keys.ts: PHOENIX_FUNNEL_READOUT key constant + its DECLARED_FLAGS entry.
  • alchemy.run.ts: the funnelReadoutFlag import + stack wiring.

Delete the flag's own tests; reword stale docblocks

  • Deleted funnelGating.ts / funnelGating.test.ts (the client FlagGate/fallback) and funnel-readout.invariant.test.ts.
  • Reworded docblocks/glossary entries that referenced the retired flag (FunnelSummary.tsx, funnel/fate-module.ts, App.tsx route comment, keys.ts precedent mentions, .glossary/TERMS.md); repointed sibling invariant-test docblocks off the deleted test file to authorship-loop.invariant.test.ts.

Verification

  • git grep: zero surviving references to the flag string/const under apps/web/ (grep-clean). The only remaining funnel-readout hits are the surviving funnel readout surface (funnel/gate.ts — the access gate, not the flag) and pre-existing packages/cf-utils CLI diff-renderer fixtures (illustrative sample data, out of scope like the reachability-guard fixtures).
  • pnpm typecheck clean; pnpm lint clean.
  • Funnel unit + edited flagship invariant tests: 54 passed. (Flagship resource tests fail on the pre-existing CF-API auth error, unrelated.)

Closes #3665

@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown

🚀 Preview deployed

  • web — Stage pr-3699 torn down.

@usirin

usirin commented Jul 19, 2026

Copy link
Copy Markdown
Member Author

review-code: PASS @ c33026f

Verified PR #3699 against #3665's acceptance criteria + ADR 0136 (machine-nominates / human-confirms via the flag-retirement label). Read all files from the PR head ref; ran typecheck / lint / the full unit project in an isolated head worktree.

Reviewed-head: @ c33026f

Acceptance criteria

  • [PASS] Gate + dead fallback removed, only the live path remains — FunnelPage.tsx drops useFlag(PHOENIX_FUNNEL_READOUT), the flag-loading placeholder, and the shouldRenderFunnelPage flag-off 404 branch (the surface always renders); funnel/queries.ts drops the readoutOn flag read + its Denied fallback. Access stays server-authoritative: the ViewFunnel-gated funnel.summary read via requireFunnelAccess is now the sole gate, and Denied is still the invisible non-mod refusal (error union unchanged). Served-on behavior preserved byte-for-byte.
  • [PASS] IaC declaration + key constant deleted, grep-clean — flagship/resources.ts: FUNNEL_READOUT_FLAG config + funnelReadoutFlag factory + import + re-export gone; flags/keys.ts: PHOENIX_FUNNEL_READOUT const + its DECLARED_FLAGS entry gone; alchemy.run.ts: import + stack wiring gone. git grep at head: zero phoenix-funnel-readout / PHOENIX_FUNNEL_READOUT / funnelReadoutFlag / FUNNEL_READOUT_FLAG / shouldRenderFunnelPage references under apps/web/ (and none repo-wide). The surviving funnel/gate.ts surface name and packages/cf-utils fixtures are out of scope.
  • [PASS] Flag's own tests removed, existing behavior tests green — funnelGating.ts / funnelGating.test.ts and funnel-readout.invariant.test.ts deleted; the five sibling flagship-invariant docblocks repointed off the deleted file to authorship-loop.invariant.test.ts. Full apps/web unit project: 293 files / 2402 tests passed, 0 failed.
  • [PASS] typecheck + lint clean — forced @kampus/web typecheck (cache-bypassed against the head worktree): 0 errors. lint:worktree (19 changed files): clean, no fixes.

Union-of-removals — the diff's + lines re-add no already-retired flag (checked pano-optimistic-submit / comment-add / comment-delete / pano-feed-edge-cache and their consts). Removals only; nothing resurrected.

Note — the integration-tests job may be red from the known D1-429 flag-provisioning flake (#690/#813); that is unrelated to this PR. The PR's own unit surface (funnel + flagship invariant, and the whole apps/web unit project) is fully green.

All four criteria pass. Product/app change, not §CP. This PR is merge-ready on the code gate. review-code does not mergeship-it is the authorized merge step.

@usirin

usirin commented Jul 19, 2026

Copy link
Copy Markdown
Member Author

review-design: PASS @ c33026f — merge-ready

Rendered PR #3699 over the preview deploy (pr-3699) and judged the changed /funnel UI surface against the ADR-0162 four-pillars design law. Judged the local captured bytes; the hosted upload returned HTTP 400 (tolerated — evidence-only, out of the decision path).

Reviewed-head: @ c33026f

Captured /funnel as an anonymous / non-mod visitor — the server-authoritative denied state this PR preserves (the flag gate is gone; access now rests solely on the ViewFunnel-gated funnel.summary read). The page renders its title + description + the designed "bu alanı görme yetkin yok." denial, caught by the page's <Screen>.

Hard-FAIL prohibitions (ADR 0162)

  • [PASS] Faint-for-meaning — /funnel: the denial status text reads at a legible muted gray, not below the AA floor
  • [N/A] Missing focus ring — no changed interactive control on the funnel surface
  • [PASS] Off-grid spacing/type — /funnel: on the 4px lattice
  • [PASS] Void empty state — /funnel: the denied state renders a designed labelled message ("bu alanı görme yetkin yok."), not a blank void
  • [N/A] Sub-36px tap target — no changed interactive control on the funnel surface
  • [PASS] Colour-alone meaning — /funnel: the denial is carried by text, not hue
  • [PASS] Render exception (review-design's single render misses intermittent mount/init races (crashes on some ticks) #2594) — no uncaught pageerror; the only page errors were the expected console.error server-authoritative denial ("Dönüşüm metriklerini görmek için moderatör olmalısın."), caught by <Screen>
  • [N/A] Golden-deviation (Decision (founder-fork): where does the perception gate live — per-PR, per-epic composition pass, or both? #2945) — no blessed surface (golden-pointer.json has an empty surfaces map)

Advisory (non-blocking)

  • The captured state is the non-mod denial; the mod-only FunnelSummary content itself is unchanged by this PR (only the flag gate was removed), so its render is out of this diff's blast radius.
  • Two console.error entries observed = the intended server-authoritative denial path, not defects.

Evidence

  • /funnel (non-mod denied state) — captured locally (funnel@desktop.png); hosted upload failed HTTP 400 (evidence-only degradation, verdict rests on local bytes).

All objective prohibitions pass. This PR is design-merge-ready. review-design does not mergeship-it is the authorized merge step.

@usirin
usirin added this pull request to the merge queue Jul 20, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 20, 2026
@usirin
usirin added this pull request to the merge queue Jul 20, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Jul 20, 2026
@usirin

usirin commented Jul 20, 2026

Copy link
Copy Markdown
Member Author

ship-it: merge-queue ejection detected — PR #3699 was re-enqueued at 2026-07-20T00:32:00Z (fresh added_to_merge_queue) but the queue dropped it 15s later without merging (still open, no longer queued, not merged). This is NOT a shipped state.

Cause: a textual conflict on the batch ref — the base moved. mergeable: false / mergeable_state: dirty, stable across four polls. Sibling flag-retirement PR #3683 (pano-optimistic-comment-add) landed on main while #3699 sat in the queue, and it edits the same three flag-IaC files this PR edits:

  • apps/web/alchemy.run.ts
  • apps/web/src/flags/keys.ts
  • apps/web/worker/features/flagship/resources.ts

Prior ejection (a different cause, now moot). The first ejection (00:06:56 enqueue → 00:16:05 removal) was the known D1-429 flag-provisioning flake (#690/#813), not a conflict: the queue's batch CI run failed with Cloudflare API POST …/d1/…/query failed: 429 {"code":971,"message":"Please wait and consider throttling your request speed"} across fts-backfill, fts-backfill-restore, sozluk-keyset, sozluk-read, and content-removal-substrate — all in the integration harness seed path (promoteToYazarrunD1Query), none related to this diff.

All PR-head gates were green at re-enqueue (c33026fe): review-code: PASS and review-design: PASS both bound to the current head, every gating check green, run-evidence bundle schemaVersion 1 with commit == c33026fe and all checks pass, zero unresolved review threads, zero comment leaks.

Routing back to repair/re-queue. The path is rebase → re-review → re-ship: the rebase moves the head, which staleness-invalidates both current-head verdicts (ADR 0058), so review-code + review-design must re-run against the new head before ship-it can re-enqueue.

…lback + IaC declaration

The conversion-funnel readout flag (#1589) serves on@100% in prod. Per ADR 0136
(machine nominates, human confirms), retire the released flag: rip the useFlag/
FlagGate gate and the dead flag-off fallback so only the live served-on path
remains, and delete the flag's IaC declaration.

- FunnelPage: drop the useFlag gate + the flag-loading placeholder + the 404
  fallback branch; the surface always renders. Access stays server-authoritative
  (the ViewFunnel-gated funnel.summary read denies a non-mod).
- funnel/queries.ts: drop the readoutOn flag check + the Denied fallback; the
  capability gate (requireFunnelAccess) is the sole gate.
- Delete the flag's IaC declaration: the FUNNEL_READOUT_FLAG config + factory +
  import/re-export in flagship/resources.ts, the PHOENIX_FUNNEL_READOUT key
  constant + DECLARED_FLAGS entry in flags/keys.ts, the alchemy.run.ts import +
  stack wiring.
- Delete the flag's own gate + invariant tests (funnelGating.ts/.test.ts,
  funnel-readout.invariant.test.ts).
- Reword stale docblocks/glossary entries that referenced the retired flag;
  repoint sibling invariant-test docblocks off the deleted test file.

Closes #3665

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@usirin
usirin force-pushed the umut/3665-retire-funnel-readout-flag branch from c33026f to a6b81e9 Compare July 20, 2026 02:41
@usirin

usirin commented Jul 20, 2026

Copy link
Copy Markdown
Member Author

review-code: PASS @ a6b81e9

Re-verified at the post-rebase head (the prior review-code: PASS @ c33026fe is stale and does not count). Verified read-only off pull/3699/head.

Acceptance criteria (#3665, ADR 0136)

  1. FlagGate/useFlag consumers + dead fallback removed; only the live path remains — PASS. apps/web/src/pages/FunnelPage.tsx drops useFlag(PHOENIX_FUNNEL_READOUT), the flagLoading placeholder, and the if (!shouldRenderFunnelPage(flagOn)) return <NotFoundPage /> 404 branch; apps/web/src/components/funnel/funnelGating.ts (+ its test) is deleted. The remaining render path is byte-for-byte the serves:on path.
  2. Access remains server-authoritative — PASS. apps/web/worker/features/funnel/queries.ts is untouched in substance: funnel.summary still wraps summaryGated() in requireFunnelAccess, summaryGated still yield* ViewFunnel, and the error channel is still Schema.Union([Denied]) — the invisible non-mod refusal is intact. The only change is docblock prose dropping the flag mention. No behavior change.
  3. IaC declaration deleted — PASS. flags/keys.ts: PHOENIX_FUNNEL_READOUT const + its DECLARED_FLAGS entry removed. flagship/resources.ts: FUNNEL_READOUT_FLAG config, the funnelReadoutFlag factory, the PHOENIX_FUNNEL_READOUT import and re-export all removed. apps/web/alchemy.run.ts: the funnelReadoutFlag import and the yield* funnelReadoutFlag(flagship.appId) wiring removed.
  4. The flag's own invariant/gating tests removed; siblings stay coherent — PASS. funnel-readout.invariant.test.ts and funnelGating.test.ts deleted; the two sibling docblocks that cited the deleted file (bildirim.invariant.test.ts, optimistic-edits.invariant.test.ts) are repointed to the surviving authorship-loop.invariant.test.ts, so no test docblock dangles.
  5. Grep-clean — PASS. git grep -nE 'phoenix-funnel-readout|PHOENIX_FUNNEL_READOUT|funnelReadoutFlag|FUNNEL_READOUT_FLAG' at this head returns zero matches across the whole tracked tree. The in-scope carve-outs (the funnel/gate.ts ViewFunnel surface name, cf-utils illustrative fixtures) are correctly untouched.
  6. Doc coherence — PASS. .glossary/TERMS.md funnel row drops "Served behind the default-off phoenix-funnel-readout flag" and the "(flag-gated)" isn't-clause while keeping the ViewFunnel mod gate as the access story.
  7. typecheck / lint / CI — PASS at this head (pnpm typecheck --force green with 0 cached; CI fully green, mergeable_state=clean; union-of-removals confirmed passing).

Net: -171/+15 across 14 files, all deletions of dead gate machinery. Not §CP.

Advisory (non-blocking, does not affect this verdict)

The rebase repointed the USER_BAN_FLAG docblock (apps/web/worker/features/flagship/resources.ts, the "mirrors ..." line near the karmaGatesFlag/USER_BAN_FLAG block) from FUNNEL_READOUT_FLAG to PANO_DRAFT_SAVE_FLAG. But pano-draft-save is itself in the retirement set (#3673), so that pointer will dangle again on the next child. A non-retiring survivor such as member-mute is the durable target. Same applies to the two invariant-test docblocks repointed to authorship-loop.invariant.test.ts if that flag is also slated. Worth a follow-up on the retirement epic, not a blocker here.

@usirin

usirin commented Jul 20, 2026

Copy link
Copy Markdown
Member Author

review-design: PASS @ a6b81e9 — merge-ready

Re-verified at the post-rebase head (the prior review-design: PASS @ c33026fe is stale and does not count).

UI-affecting surface: apps/web/src/App.tsx, apps/web/src/pages/FunnelPage.tsx, apps/web/src/components/funnel/FunnelSummary.tsx, apps/web/src/components/funnel/funnelGating.ts (deleted). Dispatched per class-probe (has-code + has-ui).

Rendered-surface delta: none. The flag serves on@100% in production, so every removed branch was already unreachable for real users:

  • FunnelPage.tsx — the flagLoading "yükleniyor…" placeholder and the flag-off <NotFoundPage /> return are deleted. At serves:on the placeholder rendered only for the sub-resolve tick and the 404 never rendered at all; what a mod sees (<main class="kp-funnel"><Screen><FunnelSummary>) is unchanged. Removing the flag round-trip strictly removes a transient loading frame — a small improvement, no new state.
  • FunnelSummary.tsx — docblock prose only. The a11y structure the docblock describes (<figure>/<figcaption> per headline rate, real <dl> for the counts) is untouched, so the kamp.us v1 — earned authorship: the çaylak→yazar proving ladder (dark-shipped) #1202 a11y baseline holds.
  • App.tsx — comment only; the /funnel route element is unchanged.
  • No CSS, token, layout, spacing, or component-selection change anywhere in the diff (Funnel.css is not touched), so the four-pillars design law (ADR 0162) surface is untouched by construction — nothing new to gate against the manifest.

Refusal states: the non-mod path stays server-authoritative — funnel.summary denies the invisible Denied/UNAUTHORIZED, caught by the page's <Screen>, so a non-mod still gets an invisible refusal rather than a rendered denial. The retirement moves the refusal from a client-side 404 to the server gate that was already the real authority; no leaked mod-only chrome, no new empty state.

No design-law violation, no visual regression, no new UI surface.

@usirin
usirin added this pull request to the merge queue Jul 20, 2026
Merged via the queue into main with commit d3169ca Jul 20, 2026
42 checks passed
@usirin
usirin deleted the umut/3665-retire-funnel-readout-flag branch July 20, 2026 05:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Retire phoenix-funnel-readout flag — rip FlagGate + fallback + IaC declaration (ADR 0136)

1 participant