docs(workflow-learnings): correct the "bounded" heuristic — a clock-shaped dep is not a fast one (#3012)

The severity heuristic I wrote in #2998 sorted dependencies **by name**,
and #3007 is the counterexample.

## What I got wrong

I classified `lifecycleDates` as *bounded* because its dep list contains
`lifecycleNowMs`, and deferred it in #3001 with the line *"any wrong
answer there survives only until the next update."*

That value is driven by a **local-midnight boundary timer** — one tick
per card per day. So a finished card shows no completion date for up to
**twenty-four hours**. @gsxdsm found it after I'd written it off.

`nowMs`, `Ticker` and `lastFetchTimeMs` span a live 30-second ticker, a
per-fetch stamp, and a daily boundary. Sorting them by name puts a
day-long defect in the same bucket as a 30-second one.

## The sharper half

A card in a **completion lane doesn't subscribe to the shared live
ticker at all** — that's exactly what the ticker's eligibility check is
for, and what #2996 fixed. So the "fast" dependency that would have
rescued this population is the one thing that population never receives.

The corrected question is: **which dependencies refresh *for this
population*** — not which ones appear in the list. Two of my three
severity calls in that sweep leaned on a dep that the affected cards
structurally never get.

## Why this is worth a PR rather than a quiet edit

The doc is what the next person triages against. #3001 explicitly told
them the four "bounded" sites were deprioritised **by design** — on
reasoning that was wrong for at least one of them. Leaving that in place
means someone defers a day-long defect on my say-so.

Docs only. No code, no baselines.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-07-31 00:33:25 -07:00
committed by GitHub
parent ffe9898710
commit e9f587c363

View File

@@ -293,10 +293,23 @@ Found: the blocker fan-out map (empty trait index, permanent), the card's live e
**Two properties decide severity, and both are checkable by reading the dependency list:**
1. **Does any dependency refresh quickly?** A live clock, `allTasks`, a task identity — any of these
rebuilds the closure on the next update, so the wrong answer is a bounded window rather than
permanent. The near-duplicate chip keys on `allTasks` and self-heals on the next task refresh; the
time indicator keys on `task.column`, which never changes, so it never recovers.
1. **Does any dependency refresh quickly?** `allTasks` or a task identity rebuilds the closure on the
next update, so the wrong answer is a bounded window rather than permanent. The near-duplicate
chip keys on `allTasks` and self-heals on the next task refresh; the time indicator keys on
`task.column`, which never changes, so it never recovers.
**A CLOCK-SHAPED DEPENDENCY IS NOT AUTOMATICALLY A FAST ONE — read its cadence, not its name.**
I classified `lifecycleDates` as bounded on the strength of a `lifecycleNowMs` dependency and
deferred it. That value is driven by a LOCAL-MIDNIGHT boundary timer, one tick per card per day,
so a finished card shows no completion date for up to twenty-four hours. The operator found it
after I had written it off. `nowMs`, `Ticker` and `lastFetchTimeMs` span a live 30-second ticker,
a per-fetch stamp and a daily boundary; sorting them by name puts a day-long defect in the same
bucket as a 30-second one.
The interaction is worth keeping too: a card in a completion lane does not subscribe to the shared
live ticker at all (that is what the ticker's own eligibility check is for), so the "fast"
dependency that would have rescued it is the one thing it never receives. Ask which dependencies
refresh FOR THIS POPULATION, not which ones exist in the list.
2. **Is the value covered TRANSITIVELY?** A dependency that itself lists the flags gets a new
identity when they arrive, which propagates. `TaskCard`'s context-menu memo omits all three role
flags and is nonetheless correct, because it depends on `taskActionMenuModel.actions` and that