Kernel: an issue's State is derived from its facts, in a module of its own

Merged
DO
3d ago
+2586-13

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, and factory:admit never 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.json gains an issues block, 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_unreadable and linked_unreadable.
  • Readiness: a factory:ready counts only if the latest completed judge-readiness Run 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, not acceptance_lapsed.
  • Same-second label adds each count, whatever their order.
  • Intent codes: JUDGE_READINESS and OPENING (open-pull-request). #42 adopts them.
  • linked trusts a linked pull request's class and doesn't check it against the opener. The port must get class right, as for pull requests; decision 0028 says so.
  • Mutants: in their own list, ISSUE, before MUTANTS, so the two Kernel lanes never edit the same lines.
  • Decision 0028. Design §3.3 and the AGENTS pages in kernel/, lifecycle/ and tests/ 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.py has 47+ tests.
  • make verify passes (581 tests) on a plain git archive export with PATH=/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_decisions is 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;
  • began counting an App's or an earlier life's labels;
  • unreadable state, label_names or issues deriving 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.