Kernel: when waiting becomes the Maintainer's to see
Part of the epic "Epic: nine gaps in the Kernel, found in review". Closes #36.
A required Lens that never reported left an item waiting for ever, never shown as the Maintainer's to see. stale_after_minutes in the Lifecycle was demanded by the check and read by nothing.
The rule
Past its State's stale_after_minutes, a wait with attention review or ready shows stale. The outcome and the act do not change. This follows the working ruling on #36 and its refinement after review.
- Measured from the item's last movement. Movement is a push, a required check's completion (new fact
completed_at), a review or thread by a person with write access or by a Lens,last_writer_action_at, and the item's run records. A stranger's comment, or a check that is not required, moves nothing, the same rule I-28 applies to attempts. - Counted only while passes run. The count starts no earlier than
passes_since, the start of the current unbroken chain of passes. An unreadable one is ignored, which shows more, never less. - Never stale:
needs_you,working,failed,none, and a deferral that holds the whole scope (paused,pause_unreadable,capacity,capacity_unreadable,concurrency). Health reports each of those once (§4.6). - A time is a real moment no later than
now. The time pattern in every schema now bounds each field, andis_timerejects a day its month does not have. A time that is not a moment, or one still to come, is passed over, so a crafted time cannot hide a stall. A review whose time is impossible isreview_unreadable. - Strictly after the limit: at exactly N minutes an item is not yet stale.
- The limits are read from
lifecycle/pull_request.jsonand reach the Kernel asconfig["lifecycle"]; a copy inconfig/defaults.jsonfails the schema.derive.staletakes any Lifecycle, so the issue Lifecycle can use it.
The calendar in the Kernel
The Kernel imports nothing and may write no number but 0 and 1, so it counts the seconds between two times itself. The calendar's units (60, 24, 365, the leap-year rule, the days before each month) are constants in kernel/terms.py. tools/check_purity.py allows exactly those names, each assigned once at the top of that file, at exactly its pinned value. Any other number, a second assignment, or an assignment to an attribute of that name is still caught (tests/test_tools.py::test_only_the_calendar_may_write_a_number). seconds() matches datetime across leap years and centuries.
Acceptance criteria
test_a_lens_that_never_reports_is_stale, forverifying,convergedandqueued: one minute before and exactly at the limit,revieworready; one minute past,stale; the Decision otherwise identical.test_staleness_counts_only_while_passes_run,test_needs_you_is_never_stale, plustest_any_movement_restarts_the_count,test_only_what_a_writer_a_lens_or_the_factory_does_moves_an_item,test_a_time_that_is_no_moment_or_still_to_come_moves_nothing,test_a_deferral_of_the_whole_scope_is_never_staleandtest_the_kernel_never_raises_on_a_time_the_schema_accepts.completed_atis in the facts schema, the example,tests/support.pyandsim/world.py, with counter-examples for a missing one and for an impossible month and day.staleis a Decision attention class.- 22 new mutants, including the limit comparison and
passes_since. 137 of 137 killed.
The simulator keeps its own clock of when an item last moved, and checks every unfaulted stale Decision against it. A stranger now comments in the simulated world (anyone_can_comment, unverified).
Documents: design §4.1, §4.4 I-1, §4.6 and §8.4; kernel/AGENTS.md and config/AGENTS.md.
Read by
A fresh-context review and a separate security pass (Claude), on the first commit; the second commit answers them. The security pass found a schema-valid time (month 13) that made the Kernel raise, which could stop a dequeue, and times that could hide a stall. Both are fixed. AGY (Gemini) to follow; Codex and CodeRabbit are at their usage limits. make verify passes on a git archive export (331 tests), and python3 -m tools.mutants kills 137 of 137.
🤖 Generated with Claude Code
Summary by CodeRabbit
- New Features
- Items waiting in review or ready states are now marked stale when they exceed their configured wait limits while processing continues.
- Bug Fixes
- Stale status reflects qualifying item activity and does not override scope-wide deferrals or change the item’s state, reason, or outcome.
- Invalid or future timestamps no longer distort stale-wait calculations.