Acceptance port: an issue asks for a contract only by an Intent that asks or a person's factory:contract label; acceptance read whole when none asks (#290)

Merged
DO
7d ago
+420-26

Closes #290. This builds the Maintainer's rule of 2026-10-21, adopted on #191 (comment 6327479372), and records it as decision 0047 (the queue order: #293 keeps 0046, this takes 0047; it is enqueued only after #293 merges). 0047 adds to decision 0025.

What it adds

  • adapters/acceptance.py reads whether the issues a pull request closes ask for an acceptance contract.
    • The closing references. They are read over GraphQL, in the shape P-33 and P-46 recorded, with each issue's repo and labels.
    • The ask. An issue asks only when its item's Intent asks (fix or implement, which no Lifecycle runs yet) or when a person adds the factory:contract label (kernel/terms.py).
    • How the label is read. It reads as the Kernel reads a control label (standing_labels): only a person ends what a person placed (L-17). An App or the machine user, even acting with a person's token, neither asks nor ends a person's ask. A removal in the same second reads as placed.
    • The labels must agree. The labels GitHub lists on the issue must match what its timeline says.
    • The result:
      • No closing issue asks: acceptance is null and read whole. Slice 1 no longer defers on every pull request.
      • An issue asks: the read is not whole until adapters/contract.py reads the contract, so the Kernel defers.
      • Not whole for any of these reasons:
        • closing references past their page, of another repo, or named twice;
        • an issue's labels past their page, or out of step with its timeline;
        • a short issue timeline;
        • a pull request to a base other than the default branch, which carries no closing reference (P-33).
      • A shape GitHub does not give is refused.
  • adapters/facts.py puts acceptance and complete.acceptance in the snapshot from this read.
  • A test pins every Intent a Lifecycle runs, so a new Intent is ruled on before it runs.
  • Rows re-pointed:
    • The rows for the contract's own read in 0015, 0018, 0022, 0025 and 0026 now wait on adapters/contract.py.
    • Whether a contract exists is held by the new tests.

The known limit

The author writes the closing references, so removing "Closes" removes the ask. That is acceptable for slice 1, because fix and implement pull requests will be opened by the Factory's own Roles. Binding a pull request to its issue by its Run record or branch is filed as #295, "Acceptance: a pull request is bound to the issue it serves by its Run or branch, not by its author's closing link". 0047 names it, and its row waits on adapters/binding.py.

Gates

GateResult
Claude review + security passthree real gaps, all fixed in one round (4e45302): an App ending a person's ask, a non-default base, and a vacuous Intent guard
Muse (Wonder Full), first headthe same blocker (an App ending a person's ask) and two minors (can_write, malformed shapes), all fixed
AGY (Argon)no findings
Capped mutant run, by handevery adapters mutant killed (216 at 42d6078; the acceptance mutants again at 7be5456)
Export verify (7be5456)exit 0, 962 tests
tools/check_decisions.py in the checkoutpasses

🤖 Generated with Claude Code