I1405 — A pull request names the issue it realises, and the edge is derived rather than remembered
Derive the relation between an issue and the pull requests, branches and commits that realised it, from git and from GitHub, and make it traversable in both directions.
DONE wave 1 · p1 · implementation profile · runs alone
Objective
Derive the relation between an issue and the pull requests, branches and commits that realised it, from git and from GitHub, and make it traversable in both directions.
Why
The model reaches as far as a branch: `worktree/state.rs` reads an issue id out of a branch name and the topology gate enforces it. Above the branch there is nothing. A merged pull request does not lead back to the acceptance criteria it satisfied, and an outcome does not lead forward to the code that reached it, which is the question the whole model exists to answer.
Current state
Evidence carries an optional `commit` string written by hand. No canonical record names a pull request, a check run or a branch. `issue_of()` in apps/majordomus-cli/src/worktree/state.rs infers the issue from a branch path component and is the only edge that exists.
Desired state
Given an issue, the branches, commits and pull requests that realised it are derivable. Given a merged commit or a pull request, the issue and the outcome it served are derivable. Nothing is stored that git or GitHub already knows.
Scope
- apps/majordomus-cli/src/worktree
- apps/majordomus-cli/src/git
- apps/majordomus-cli/src/graph.rs
- scripts/github-sync
- test/cases
- docs
Out of scope
- Requiring a branch to encode an issue id where doctrine does not already require it
- Storing a commit list in a canonical record; git owns commits
Dependencies
What waits on this
Acceptance criteria
- Given an issue id, the branches, commits and pull requests that realised it are listed, derived from git and GitHub
- Given a commit or a pull request, the issue and the milestone it served are named, or it is reported as unattributed
- Work with no execution contract is reported as unattributed rather than silently omitted
- The derivation reads no stored edge that git or GitHub already holds
Validation
- cargo test --workspace --all-features
- bash test/run.sh
Evidence required
- traceability_both_ways
Evidence
| covers | type | command | result | at commit |
|---|---|---|---|---|
| traceability_both_ways | test | env -u MAJORDOMUS_SHARE bash test/run.sh 98_traceability | ok 98_traceability: over real MCP frames, I0001 (merged with a merge commit) lists its branch, its merge commit and its two commits; I0002 (open) is measured against the trunk; I0003 (fast-forwarded) reports itself absorbed and claims none of the trunk's commits; I0004 (declared, unstarted) traces to nothing and stays declared; I9999 answers declared:false. Backwards, a commit of the merged branch names I0001 and milestone M0, and the commit made straight on the trunk is reported unattributed with a reason. The report's buckets sum to the commits examined, and the absorbed branch's commit is present in it rather than omitted. Then scripts/traceability over a pull-request fixture joins PR #1 to I0001, answers #2 with I0002 and M0, reports #3 (head branch chore/tidy-up) unattributed rather than dropping it, tallies 3 read / 2 attributed / 1 unattributed, and exits 10 under --strict on a commit with no contract. Finally: no canonical record under .ai/repo/project/ names a branch after the run, and git status is unchanged. | 990bd05 |
Risk
A commit-to-issue edge inferred from a branch name is only as good as the branch discipline; unattributed must be a reported state, not an assumption.
Timeline
- started
- 2026-09-09T18:55:15Z
- verified
- 2026-09-09T18:55:34Z
- completed
- 2026-09-09T18:55:36Z
Those three fields, the evidence above and the state of the dependencies are all the status is made of. There is no status field to disagree with them.
Canonical record: .ai/repo/project/issues/I1405.yaml. Read it back with majordomus plan show I1405.