Kernel: an issue's State is derived from its facts, in a module of its own
Merged
DO
3d ago+2586+2586+0123456789012345678901234567890123456789-13-13-01234567890123456789
Closes #41.
The first story in the issue-side lane of the Kernel. An issue's State is now derived from its facts, in a module of its own, kernel/issue.py. It doesn't edit decide.py, derive.py, terms.py or gate.py, and imports only five helpers from them. The rulings are on #41.
What changes
kernel/issue.py:derive(facts, runs, config)gives an issue's State, reason and evidence. Labels are read as events, as on a pull request: only a person's label counts, or the declared App's, andfactory:admitnever counts for an App. A person's Hold and hands-off are reused.- Its own schema:
schemas/issue-facts.schema.json, with an example and six counter-examples. - Configuration:
config/defaults.jsongains anissuesblock, required by the config schema. Its defaults are null, so no issue is served until the Maintainer turns issues on. - Unreadable or short facts defer:
state_unreadable,label_unreadable,type_unreadable,issues_unreadableandlinked_unreadable. - Readiness: a
factory:readycounts only if the latest completedjudge-readinessRun that started no later than the label judged the current content.- A Run still running doesn't count, and neither does a Run of another Intent.
- Working ruling: an expired Run record doesn't count either, so the issue stays in triage (fail closed).
- "Work began" needs a person's admission at some point, plus a linked pull request or a triage or ready label added in this life by a person or the declared App. Otherwise an outsider's issue is
not_accepted, notacceptance_lapsed. - Same-second label adds each count, whatever their order.
- Intent codes:
JUDGE_READINESSandOPENING(open-pull-request). #42 adopts them. linkedtrusts a linked pull request'sclassand doesn't check it against the opener. The port must getclassright, as for pull requests; decision 0028 says so.- Mutants: in their own list,
ISSUE, beforeMUTANTS, so the two Kernel lanes never edit the same lines. - Decision 0028. Design §3.3 and the AGENTS pages in
kernel/,lifecycle/andtests/are updated.
Test cases: the issue says 35; the seed script has 33. The two added are "a Factory ready whose Run record expired" and "a ready a person removed". "A Run that judged other content" is a boundary test of its own.
Verified:
tests/test_issue.pyhas 47+ tests.make verifypasses (581 tests) on a plaingit archiveexport withPATH=/usr/bin:/bin. The record was renumbered there only, to close the gap until 0027 merges.- Each of the 84 mutants was killed by its own test, checked one at a time by hand. No local mutation run: CI's shards run the set.
check_decisionsis clean.
Reviewed by: a fresh-context Claude review, a security pass, and AGY (Gemini 4 Argon) on 31c64cb. All their findings are fixed in 9533349:
- readiness read from any Run, or from one still running;
- an expired record counting on time alone;
begancounting an App's or an earlier life's labels;- unreadable
state,label_namesorissuesderiving a State; - missing boundary tests;
- a same-second tie read by list order.
Order: merges after #153 (decision 0027).
🤖 Generated with Claude Code
Summary by CodeRabbit
-
New Features
- Added issue lifecycle tracking, with states reflecting issue activity, linked pull requests, acceptance, and completion.
- Added configuration options to control which issues are included and which labels or issue types are excluded.
- Factory-added readiness labels count only when supported by a completed readiness check for the issue’s current content.
-
Documentation
- Clarified how issue states are determined, including readiness-label and linked pull request rules.