Skip to content

Trials P-33 and P-34: the closing reference and an issue's content time are readable #184

Closed

Description

@don-petry

Part of the epic "Epic: slice 1, first approval. A Release on next reviews a pull request to this repo, and the machine user's approval stands".

Why. Design §6.1 says P-32, P-33 and P-34 "are run before the Factory's own author is built". P-32 is in #13. P-33 and P-34 had no story, and kernel/issue.py (decisions 0028, 0031, 0034) already reads the facts they ask about: which issue a pull request closes and who opened it, content_changed_at, and each reopening with its actor. The issue Kernel rests on two unread platform facts.

Done when

  • P-33: the issue a pull request closes is read from a draft, an open, a merged and a closed-unmerged pull request alike, with its opening actor; a pull request to a branch that is not the default is tried; who last changed the draft flag is read. Each "no" takes the fallback §6 names, and opens an issue against the design.
  • P-34: the time an issue's title or body last changed is read apart from the time anything on it changed; each reopening is read with who did it.
  • Fixtures trials/P-33.json and trials/P-34.json ground the issue world's rules in sim/grounding.py (Simulator: issues, with the faults the reviews imagined #43 adds them), and tools/check_trials.py passes.

Needs. How a trial is run. The trial-author App or the Maintainer's login on the canary; no machine-user token. Run after #27 and before #43 builds the issue world on these facts.

Design. §6, P-33 and P-34; §6.1 last paragraph; §4.1.

Activity

  1. don-petry commented on Oct 20, 2026

    @don-petry
    ContributorAuthor

    Working rulings (overturnable in review)

    Solution Architect, delegated. On main at 31800f6 (#221). Read from the issue, design §6 (P-33, P-34), §6.1's last paragraph, §4.1 (the issue Lifecycle: the linked pull request, the content, a reopening), kernel/issue.py (what it reads of linked, content_changed_at, reopened_last_by), trials/P-32.json (#13) as the form of a read on an issue, .claude/skills/run-a-platform-trial/SKILL.md, and the plan review's note that these two trials run before the Factory's author is built. #27 is closed: the canary's machine-user token is revoked, which these trials never needed.

    What this story decides. Two platform facts the issue Kernel already rests on, read for the first time: which issue a pull request closes, in every state a pull request can be in, with who opened it and who last changed its draft flag; and when an issue's title or body last changed, apart from anything else on it, with each reopening and who did it. Each answer becomes a fixture that grounds a rule of the issue world (#43, ruling 6), or a "no" that takes the fallback §6 names and opens an issue against the design.

    1. Who acts, and where. Only the canary, as the Maintainer's own gh login, with no machine-user token and no minted credential. The trial-author App is not needed: every object here is one a person makes. A workflow in the canary is not needed either: every read is a REST or GraphQL call the reader App will make, so the trial records the read as the reader would, which is the fixture's value. Why: §6.1 puts these before the author is built, and the skill's rule is that a step needing another identity runs only where that identity lives; none does.

    2. P-33, the closing reference. The pass condition is §6's: the issue a pull request closes is read, with its opening actor, from a draft, an open, a merged and a closed-unmerged pull request alike; a pull request whose base is not the default branch is tried; who last changed the draft flag is read.

    • One issue, then four pull requests whose descriptions each hold Closes #N: one left a draft; one marked ready and left open; one merged; one closed unmerged. A fifth, to a branch that is not the default, left open.
    • The merge: the canary's ruleset holds its default branch, so the merged case runs first on the non-default base, where no ruleset stands, and on the default branch only if the ruleset lets the Maintainer merge their own pull request; if it does not, the fixture says so and the merged case is the non-default one. The draft flag: mark ready, then convert back to draft, and read who did each.
    • The read, in one GraphQL query per pull request as the reader will make it: closingIssuesReferences with the issue's number and author, isDraft, state, mergedAt, closedAt, baseRefName, and the timeline's ReadyForReviewEvent and ConvertToDraftEvent with their actors; and the reverse read, from the issue, timelineItems of CONNECTED_EVENT, DISCONNECTED_EVENT and CROSS_REFERENCED_EVENT, since §4.1 reads the join "at every pass". The fixture keeps both directions.
    • Each "no" takes §6's fallback and opens an issue against the design: a closed pull request that lost the reference, closes counted by the work's branch name; a non-default base that carries none, the issue Lifecycle serves the default branch only; a draft flag whose changer cannot be read, a linked pull request that has once left draft and is a draft again is treated as taken in hand.

    3. P-34, an issue's content time. The pass condition is §6's: the time the title or body last changed, apart from the time anything on it changed; each reopening, with who did it.

    • One issue; edit the body, then the title, then comment and label it, each a minute apart, so that updatedAt moves at each while the content's time moves at two.
    • The read: the issue's userContentEdits (editor and editedAt, for the body) and the timeline's RenamedTitleEvent (actor and createdAt, for the title) against updatedAt; and the timeline's ReopenedEvent with its actor after a close and a reopening by the Maintainer. content_changed_at is the newest of the two kinds of edit, as the port will compute it; the fixture records that computation beside the raw reads.
    • A reopening by someone without write access is the same ReopenedEvent with another actor, whose kind and access P-32 reads from the association; no outside account is needed to show the field, and none exists (Design: where the last pusher and the base are read from (P-28, P-29) #163's ruling on a fork's head). The fixture says the outsider's case was not tried and why.

    4. Fixtures and grounding. trials/P-33.json and trials/P-34.json, each trial/1, redacted by tools/redact_trial.py, with the verdict against the condition written before the run. In sim/grounding.py, set the fixture of each rule #43 added for them: a_closing_reference_is_read_from_any_pull_request and who_changed_the_draft_flag_is_read to P-33's; an_issues_content_time_is_read_apart and a_reopening_names_its_actor to P-34's. If #43 has not merged first, this story adds those four rules itself, unverified in the fake until the issue world applies them, and #43 marks them where it applies them. tools/check_trials.py passes either way.

    5. Record and design. No new decision: nothing is decided, two facts are read. §6's rows for P-33 and P-34 gain "Passed on (trials/P-33.json)" or the fallback taken; §6.1 is unchanged. A "no" is an issue against the design, not an edit made here.

    6. Files, and shared files. New: the two fixtures. Edited: sim/grounding.py (fixture cells of four rules, or the four rules appended), the design's §6 rows. Shared with lane b: sim/grounding.py, appended or cell-edited only.

    7. Gates. python3 -m tools.check_trials before any git add; export verify; a fresh Claude review and a security pass (the held-out and credential rules: nothing in a fixture names a credential, and the canary is left as it was, step 8 of the skill); Muse; no AGY, since neither kernel/ nor sim/world.py changes; the PR is the Maintainer's to open and says "Closes #184", the verdicts, what was removed and what was undone.

    8. Out of scope. P-32 (#13, done); the port that will make these reads (adapters/); an outsider's reopening; the hand-over itself (§4.1), which reads these facts once built.

    Needs the Maintainer? None, unless the canary's ruleset refuses the Maintainer's merge of their own pull request on the default branch and the Maintainer would rather see that case than take the non-default one; the ruling takes the non-default one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions