Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/architecture/software-factory-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -1420,7 +1420,7 @@ write access.

| # | If | State | Next actor |
|---|---|---|---|
| 1 | A linked pull request merged with a non-empty diff in its present life and closed the issue, as the issue's timeline names it the closer, or merged while the issue stood closed; never by its references as they stand now, which an edit after the merge can change (P-46, issue 267). Never an admitted outsider's merge, whose author may add a closing keyword after the approval: it reads `declined`, `closed_by_outsider_merge`, and a person decides, after the contradictions below are read (P-48, issue 280). One that closed nothing, of an issue still open, waits quietly within `max_close_lag_seconds` of the merge, `merge_closing`, since GitHub closes the issue a second after the merge and records the close two (P-33), and is read past after it. Unless, when the first such merge landed, a person had closed the issue in this life, anyone but a writer had reopened it, or a Hold stood on the issue (whoever placed it, or hands-off a person did not place) with no person's hands-off beside it: then the State a person put it in stays, `declined` or `held`, and the reason names the contradiction, `merged_while_declined` or `merged_while_held` (§4.5, issue 227). So does a State only a person ends that the facts place at that merge, read before the Hold: a person's hands-off on the issue no person has removed since, `not_served`, `merged_while_hands_off` ("A platform close keeps the State a person set on an issue held or not served", issue 522). Only the issue's own labels are read at the merge: a linked draft's Hold or hands-off is not, since when it became linked carries no time (decision 0141). The Consumer entry, whose configuration has no history, a left-out label or type, and when a pull request became linked carry no time in the facts and read as before, so that no terminal State is left by them ("Trial P-61: an issue's type changes and a pull request linked by hand, each timed on the issue's timeline", issue 551; "Facts: a closing keyword's link timed by the first pass that saw it, and the Consumer entry at the merge (after the first cut)", issue 552) | `done` | None |
| 1 | A linked pull request merged with a non-empty diff in its present life and closed the issue, as the issue's timeline names it the closer, or merged while the issue stood closed; never by its references as they stand now, which an edit after the merge can change (P-46, issue 267). Never an admitted outsider's merge, whose author may add a closing keyword after the approval: it reads `declined`, `closed_by_outsider_merge`, and a person decides, after the contradictions below are read (P-48, issue 280). One that closed nothing, of an issue still open, waits quietly within `max_close_lag_seconds` of the merge, `merge_closing`, since GitHub closes the issue a second after the merge and records the close two (P-33), and is read past after it. Unless, when the first such merge landed, a person had closed the issue in this life, anyone but a writer had reopened it, or a Hold stood on the issue (whoever placed it, or hands-off a person did not place) with no person's hands-off beside it: then the State a person put it in stays, `declined` or `held`, and the reason names the contradiction, `merged_while_declined` or `merged_while_held` (§4.5, issue 227). So does a State only a person ends that the facts place at that merge, read before the Hold: a person's hands-off on the issue no person has removed since, `not_served`, `merged_while_hands_off` ("A platform close keeps the State a person set on an issue held or not served", issue 522). Only the issue's own labels are read at the merge: a linked draft's Hold or hands-off is not, since when it became linked carries no time (decision 0141). Two pull requests linked by hand before the first merge's second, neither closed before it nor unlinked since, an outsider's only where a person had admitted it by then, were two links at it, and keep it `held`, `merged_while_held`, until fewer than two stand, an unlinking releasing it for good as a Hold's removal does (who can unlink is for trial P-61 to show); one linked in that second, or by a closing keyword alone, reads as before, so the Factory's merge beside another in its second is done ("Trial P-61: an issue's type changes and a pull request linked by hand, each timed on the issue's timeline", issue 551). The Consumer entry, whose configuration has no history, a left-out label or type, and when a pull request became linked by a closing keyword carry no time row 1 reads, and read as before, so that no terminal State is left by them ("Facts: a closing keyword's link timed by the first pass that saw it, and the Consumer entry at the merge (after the first cut)", issue 552) | `done` | None |
| 2 | Closed in any other way, an empty merge and a commit's closing keyword among them. It stays so if anyone but a person with write access reopens it | `declined` | None |
| 3 | The Consumer entry does not serve it; or it, or its linked draft, carries `factory:hands-off` | `not_served` | A person |
| 4 | It carries `factory:hold`, whoever added it; or its linked draft pull request does; or more than one linked pull request is open; or its Run ceiling is reached while it wants a Run (decision 0003) | `held` | As the Hold states |
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 0141. A linked draft's flag is read from its timeline, and no linked draft's label holds an issue at its merge

Status: accepted. Replaces `docs/decisions/0139-a-platform-close-keeps-the-state-a-person-set-on-an-issue-held-or-not-served.md`, and keeps all of it but what it reads of a linked draft at the merge, which it overturns: that a Hold on a linked draft, hands-off anyone but a person placed on one, and a person's hands-off on one that lets a Hold pass, are read from the draft's flag and the link as they stand now. The Factory Builder's rulings of 2026-11-04 on "Facts: the times of a link, a left-out label and an issue's type, so that row 1 reads them at the merge" (issue 542), of "Epic: slice 1, first approval. A Release on next reviews a pull request to this repo, and the machine user's approval stands" (issue 182): its split three ways, into this change, "Trial P-61: an issue's type changes and a pull request linked by hand, each timed on the issue's timeline" (issue 551) and "Facts: a closing keyword's link timed by the first pass that saw it, and the Consumer entry at the merge (after the first cut)" (issue 552); and ruling (ii) on this change's reads.
Status: replaced by `docs/decisions/NNNN-two-pull-requests-linked-by-hand-before-an-issues-merge-leave-it-held-and-its-type-changes-are-read-whole.md`, which holds the rule in force.

## Decision

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# NNNN. Two pull requests linked by hand before an issue's merge leave it held, and its type changes are read whole

Status: accepted. Replaces `docs/decisions/0141-a-linked-drafts-flag-is-read-from-its-timeline-and-no-linked-drafts-label-holds-an-issue-at-its-merge.md`, and keeps all of it but what it says carries no time of a link by hand and of an issue's type, which it overturns: a link by hand carries its time, and two such links before the first merge's second keep the issue `held`. It amends decision 0132's row "What stays", whose contradictions it joins. The Factory Builder's rulings of 2026-11-09 on "Trial P-61: an issue's type changes and a pull request linked by hand, each timed on the issue's timeline" (issue 551): its plan, and the narrowing of decision 0132's same second.

## Decision

| What | Decided | Because |
|---|---|---|
| The fact | Each linked pull request in `issue-facts/1` carries `draft_events`: each conversion to a draft and each marking ready on its timeline, with its time, in the timeline's order, which is the order they happened. One opened as a draft has none for it; each changes the flag, so the two alternate; and the last is its flag now. A flag that disagrees with its last event, two of one kind in a row, a time out of order, or an event that cannot be read defers, `linked_unreadable`; two in one second, in order, are read | `trials/P-33.json`: a canary pull request marked ready, converted to a draft and marked ready again has each as its own event, a second or two apart; one left a draft from its opening has none. An unreadable fact never yields an action, and an author converting and marking ready in one second must not stall the issue (Claude's security read, Codex) |
| The facts of a link by hand and a type | Each linked pull request carries `link_events`: each link of it to the issue by hand, in the issue's Development box, and each unlinking, with its time, in the timeline's order. A closing keyword's link raises none, and one with none carries no time. The issue carries `type_events`: each setting, change and removal of its type, the type before and after, with its time. Each is read whole: links alternate, the first a link, none before the pull request opened; each type change follows from the last, and the last leaves the type now. One that cannot be read, or breaks that order, defers, `linked_unreadable` or `type_unreadable`; two in one second, in order, are read | An unreadable fact never yields an action. The trial the Status names asks GitHub for each as its own timed event; until its fixture is recorded the port's reading is not grounded, and the issue world links nothing by hand and sets no type |
| Two links at the merge | Two pull requests linked by hand before the first merge's second, neither closed before it, were two links at it: the issue stood `held` (row 4), and the merge keeps it so, `merged_while_held`, naming the two, whichever was linked first and whether the queue merged one or both in that second. Each is read as it stood at the merge: an outsider's only where a person had admitted it by then, so no admission after the merge moves a done issue. One unlinked in the merge's second or after it lets the merge read on for good, as a Hold's removal does: a link made again since moves nothing, so no one moves a done issue back and forth. Link events carry no actor, so who unlinked it is not read: until trial P-61 shows who can unlink, an App or a pull request's author that can would end the hold, a change from held to done the Lifecycle declares only a person's. That gap is open, and tracked by the trial. A person's hands-off that stood beside them at the merge excuses them, as it excuses a Hold: the issue stood not served then, row 3 before row 4. The rule is read after the issue's own Hold, as a contradiction of decision 0132, and so before an admitted outsider's merge, which with two such links reads `held`, not `declined`; either way a person decides. A pull request listed twice defers, `linked_unreadable`, since it would count as two links | Done from `held` is a change by the platform the Lifecycle does not declare; record 0141's "What carries no time" left it open for want of a link's time, which a link by hand now carries. A pull request's class is read as it stands now, so one whose author gained write access after the merge counts; granting it is the Maintainer's act. Who can link and unlink by hand is for trial P-61 to show |
| The same second | A link made in the first merge's second, a closing keyword's link with no time, a link made after the merge, one undone before it, and a pull request closed before it are no second link at it: decision 0132's Factory's or person's merge beside another in its second is still `done`. That same-second rule now covers only a rival link made in that second or carrying no time | In one second no order can be told, and a keyword's link carries no time until "Facts: a closing keyword's link timed by the first pass that saw it, and the Consumer entry at the merge (after the first cut)" (issue 552) |
| How the flag reads at a time | A linked pull request was a draft at a time where its flag now, with every event after that second undone, says so, or where it was one at any moment of that second; before its first event it was what that event undid. Nothing in the Kernel reads it yet: row 1 needs, beside the flag, when the pull request became linked, which "Facts: a closing keyword's link timed by the first pass that saw it, and the Consumer entry at the merge (after the first cut)" (issue 552) is to give for a closing keyword's link | So the fact is ready the day a link's time can be proven, and its reading is decided now, not then |
| Row 1 at the merge | Row 1 reads only the issue's own labels at the first merge: a person's hands-off on the issue keeps `not_served`, `merged_while_hands_off`; a Hold on the issue, or hands-off no person placed, keeps `held`, `merged_while_held`, unless a person's hands-off on the issue stood beside it. No linked pull request's label is read there | No fact with no time moves a terminal State, in either direction: the Factory Builder's ruling (A) on "A platform close keeps the State a person set on an issue held or not served" (issue 522), held whole by ruling (ii) here. When a pull request became linked carries no time: one opened before the merge may have been named by a keyword an edit added after it (`trials/P-46.json`), so its labels, read at the merge, could move a `done` issue to `held` or `not_served`, and read as they stand now, its flag could too |
| What row 1 no longer reads | A Hold on a linked draft, hands-off anyone but a person placed on one, and a person's hands-off on one that let a Hold on the issue pass, each of which decisions 0028 and 0139 read from the draft's flag and the link as they stand now | The flag now was itself a fact with no time; taking it out completes ruling (A) rather than weakening it |
| What carries no time | When a pull request became linked by a closing keyword, the Consumer entry, whose configuration has no history, and a left-out label, whose list is the entry's as it stands now. An issue's type changes carry their times, but row 1 does not read the type at the merge while the entry, which names the types it leaves out, has no history. So the queue's merge under a Hold or hands-off on a linked draft, on an issue the entry leaves out, or from two links one of which is a keyword's alone may still leave `held` or `not_served` by the platform, a change the Lifecycle does not declare; issue 552 gives a keyword's link the time of the first pass that saw it, and leaves the entry, as ruled, until a Consumer needs it. A rival's close is read as it stands now: GitHub clears it when the pull request is reopened, so a reopening after the merge of one closed before it would move a done issue to held, until an unlinking ends it for good. That is open: "Facts: a linked pull request's close and reopening, so that row 1 reads a rival as it stood at the merge" (issue 672) | GitHub dates a keyword's link at the first mention of the issue, not at the keyword (`trials/P-48.json`); a left-out label or type read at the merge against today's list would bring back what ruling (b) on "A platform close keeps the State a person set on an issue held or not served" (issue 522) took out |
| The exploration | The queue merges the Factory's and an admitted outsider's ready pull request from a Hold or a person's hands-off on the issue itself, and from two links each made by hand, never under a linked draft's labels or from two links one of which is a keyword's alone; it records each change of a draft's flag and each link by hand | Each of those is placed at the merge; the rest wait on a link's time |
| What stays | Everything else record 0141 decides, as it words it, and with it decision 0139, decision 0028 and decision 0132 as 0141 keeps them, 0132's same second narrowed as above; and 0141's Boundaries, each held by the check it names there, with those below | Only what carries no time of a link by hand and a type is overturned |

## Why

Record 0141 stopped reading a linked draft's labels at the merge because when a pull request became linked carried no time, and left two links at the merge, a type and the Consumer entry as they stood. An issue's timeline gives a link by hand and a type change each as its own event with its time, as the trial the Status names asks, so a link by hand can be placed at the merge: two of them keep the issue held. A keyword's link, a draft's labels and the entry still wait.

## Boundaries

| Boundary | Check |
|---|---|
| Each linked pull request carries its links by hand and unlinkings, each with its time, in the timeline's order, alternating, the first a link, none before it opened; one in its opening second, and an unlinking and a link in one second, are read; one that cannot be read, or breaks that order, defers, and so does a pull request listed twice | `tests/test_issue.py::test_an_unreadable_linked_pull_request_defers`, `kernel/issue.py`, mutant: "a pull request's link events are not read", mutant: "link events that are no list are read", mutant: "a link event may be anything", mutant: "the first link event may be an unlinking", mutant: "a link may be made before the pull request opened", mutant: "a link in the opening second is refused", mutant: "link events may be out of order", mutant: "a link event may repeat the last", mutant: "a pull request listed twice is read" |
| An issue carries its type changes, each the type before and after with its time, each a change, each from the last, the last the type now; one that cannot be read, or breaks that chain, defers | `tests/test_issue.py::test_an_issues_type_changes_are_read_whole_or_defer`, `kernel/issue.py`, mutant: "an issue's type events are not read", mutant: "type events that are no list are read", mutant: "a type event's time may be anything", mutant: "a type event's names may be anything", mutant: "type events may be out of order", mutant: "a type event may not follow the last", mutant: "the last type event may leave another type", mutant: "a type change that changes nothing is read" |
| Two pull requests linked by hand before the first merge's second, neither closed before it, keep the issue held, `merged_while_held`, naming the two, whichever was linked first and when two queue merges land in one second; a rival linked in that second, with no time, after the merge, undone before it or closed before it, and the merge's own link made in its second, leave it done (decision 0132); an outsider's admitted after the merge is no link at it, and an unlinking since, after the merge or in its second, lets the merge read on for good, a link made again after it moving nothing; a person's hands-off beside them at the merge, removed since, excuses them | `tests/test_issue.py::test_two_links_by_hand_before_the_merge_leave_the_issue_held`, `kernel/issue.py`, mutant: "two links by hand at the merge are not read", mutant: "one link by hand is two at the merge", mutant: "a link in the merge's second stands before it", mutant: "an unlinking before the merge leaves the link", mutant: "a link with no time stands before the merge", mutant: "a pull request closed before the merge is a link at it", mutant: "a pull request closed in the merge's second is no link at it", mutant: "an admission after the merge counts toward two links", mutant: "a link unlinked since the merge still holds", mutant: "a link made again after an unlinking since the merge holds again", mutant: "an unlinking in the merge's second leaves the link", mutant: "a person's hands-off beside two links does not excuse them" |
| The queue's merge from held by two links each made by hand is explored and makes no change of State by the platform from held | `tests/test_transitions.py::test_the_queues_merge_from_held_or_not_served_keeps_the_state`, `kernel/issue.py`, mutant: "two links by hand at the merge are read as before, in the exploration" |
7 changes: 5 additions & 2 deletions examples/invalid/issue-facts.a-close-time-in-words.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"can_write": true
},
"type": "Task",
"type_events": [],
"label_names": [
"kernel",
"factory:triage"
Expand Down Expand Up @@ -54,7 +55,8 @@
"empty": false,
"label_events": [],
"closed_issue_at": null,
"draft_events": []
"draft_events": [],
"link_events": []
},
{
"number": 140,
Expand Down Expand Up @@ -83,7 +85,8 @@
}
],
"closed_issue_at": null,
"draft_events": []
"draft_events": [],
"link_events": []
}
],
"role_comments": [
Expand Down
Loading
Loading