Kernel: when waiting becomes the Maintainer's to see

Merged
DO
5d ago
+1037-60

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, and is_time rejects 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 is review_unreadable.
  • Strictly after the limit: at exactly N minutes an item is not yet stale.
  • The limits are read from lifecycle/pull_request.json and reach the Kernel as config["lifecycle"]; a copy in config/defaults.json fails the schema. derive.stale takes 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

  1. test_a_lens_that_never_reports_is_stale, for verifying, converged and queued: one minute before and exactly at the limit, review or ready; one minute past, stale; the Decision otherwise identical.
  2. test_staleness_counts_only_while_passes_run, test_needs_you_is_never_stale, plus test_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_stale and test_the_kernel_never_raises_on_a_time_the_schema_accepts.
  3. completed_at is in the facts schema, the example, tests/support.py and sim/world.py, with counter-examples for a missing one and for an impossible month and day. stale is a Decision attention class.
  4. 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.