Skip to content

work-graph-github — The projection to GitHub is gated, identified and traversable, and a merged pull request leads back to the outcome it served

The projection cannot rot silently: a gate runs the drift check on every change that can move the model or the adapter, and drift is a CI failure with the record named. Identity travels with the record rather than being parsed out of its title, so renaming a GitHub title moves nothing and creates no duplicate, and the remote can be a fixture, so which record is which is provable with no network. The reconciliation vocabulary distinguishes the states it currently conflates, so `missing` no longer stands for both a record never created and a record a person deleted, and a remote edit is reported as a remote edit. Above the projection, a pull request names the issue it realises and the edge is derived from git and GitHub rather than remembered, so a merged commit leads back to the acceptance criteria it satisfied and an outcome leads forward to the evidence that it holds. The derivations the shell already computes — ready, blocked, waves, next, status — are readable through the same capability surface as every other thing this repository knows, rather than only from a terminal.

ACTIVE 7 of 8 issues done

ready
1
blocked
0
active
0
verify
0
done
7
cancelled
0

Problem

This repository already models work once: milestones are outcomes, issues are execution contracts, no status is stored, and `scripts/github-sync` projects both onto GitHub with a hash-marked region that protects what a human wrote. The model is not the defect. The defect is that nothing runs the detector: `core-check` runs `github-sync --plan` and `--render`, never `--check`, so the adapter's exit 11 has never fired in CI. The projection was applied once on 2026-09-04 and has not been applied since. Measured on 2026-09-09 at 867f3a9: 191 drift findings, of which 172 are canonical issues that have never existed on GitHub and 19 are bodies whose canonical text has moved on. All 8 milestones are in sync, which is exactly what makes the rot invisible: the surface a person looks at is the surface that still agrees. Underneath that, the adapter identifies a remote record by parsing its title for an `I0001 — ` prefix and reads the remote through a 200-item page that already holds 19 issues and 117 pull requests, so the mapping breaks when a person renames a title and the read truncates as soon as the backfill runs.

Outcome

The projection cannot rot silently: a gate runs the drift check on every change that can move the model or the adapter, and drift is a CI failure with the record named. Identity travels with the record rather than being parsed out of its title, so renaming a GitHub title moves nothing and creates no duplicate, and the remote can be a fixture, so which record is which is provable with no network. The reconciliation vocabulary distinguishes the states it currently conflates, so `missing` no longer stands for both a record never created and a record a person deleted, and a remote edit is reported as a remote edit. Above the projection, a pull request names the issue it realises and the edge is derived from git and GitHub rather than remembered, so a merged commit leads back to the acceptance criteria it satisfied and an outcome leads forward to the evidence that it holds. The derivations the shell already computes — ready, blocked, waves, next, status — are readable through the same capability surface as every other thing this repository knows, rather than only from a terminal.

Current state

The canonical model is 15 milestones and 184 issues, validated by `majordomus plan validate` (0 failures, 4 scope warnings) and enforced by `lib/doctor.sh` and `scripts/ci/core-check`. `scripts/github-sync` implements plan, check, apply and render with field ownership and a marker hash. GitHub holds 8 milestones and 19 issues. `--check` exits 11 on drift and no gate calls it. Issues and milestones are served as documents through `objects.get`, and none of the plan's derivations is a capability: `lib/plan.sh` computes them and the Rust executable reads `.ai/repo/project/issues/` for filenames only. Evidence carries an optional `commit` string and no pull request, check run or branch.

Desired state

A contributor moves an issue, runs the gates, and CI tells them the projection is behind before a reviewer does. A person renames an issue on GitHub and the next sync updates it rather than duplicating it. A reader asks which pull request realised an issue, or which outcome a merged commit served, and one command answers from derived edges. The plan's readiness is answerable over MCP and HTTP because it is a capability, not a shell function.

Scope

  • scripts/github-sync
  • scripts/ci
  • .ai/repo/ci/gates.yaml
  • .ai/repo/project
  • .ai/repo/rules/project
  • .ai/repo/adrs
  • apps/majordomus-cli/src
  • lib/plan.sh
  • lib/project.sh
  • docs
  • site
  • test/cases

Out of scope

  • Replacing the canonical model, which is not the defect
  • Importing GitHub semantics into canonical records; GitHub stays a projection
  • Moving network calls into the tool itself, which test/cases/08_no_forbidden_constructs.sh forbids
  • A GraphQL client, or any provider abstraction beyond the one GitHub host this repository publishes to
  • Assignees, reactions, projects boards or discussions

Acceptance criteria

  • A gate runs the drift check on every change that can move the model or the adapter, and a drift finding fails CI naming the record and the remedy
  • Identity is canonical data: renaming a title on GitHub moves the mapping with it and creates no duplicate record
  • Reconciliation is provable offline against a fixture remote, and a fixture is refused for any mutation
  • The reconciliation vocabulary distinguishes never-created, deleted-remotely, edited-remotely, canonically-moved and conflicting, and each state names what would change before anything changes
  • A pull request is related to the issue it realises by a derived edge, and the relation is traversable in both directions
  • The plan's derivations are readable through the capability surface, and adding one requires no edit to a transport registry
  • Every claim this milestone makes about GitHub is backed by a test that runs offline against a fixture

Validation

  • scripts/github-sync --check
  • scripts/ci/core-check
  • bash test/run.sh
  • cargo test --workspace --all-features
  • majordomus generate --check

Evidence required

  • drift_gated
  • identity_stable
  • offline_provable
  • traceability_both_ways

Risks

  • The backfill is an outward-facing mutation: applying the projection creates roughly 180 public issues in one run. It must be a deliberate, confirmed act, not a side effect of wiring a gate.
  • Wiring the gate before the backfill makes CI red on master until the backfill lands. The order matters and is part of the work.

Issues

Each one is an execution contract with its own acceptance criteria, validation command and required evidence. The wave column is the layer of the dependency graph it sits in.

issuestatuswavedepends ontitle
I1401 DONE 0 The identity of a projected record is what the record carries, not what its title says
I1402 DONE 1 I1401 A finding says which of six states it found, and the state that could hide a person's edit is no longer one of them
I1403 READY 3 I1401, I1402, I1404 Backfill the projection once, deliberately, and record what it created
I1404 DONE 2 I1402 The drift check is a gate, so a projection that stops being applied cannot stay silent
I1405 DONE 1 I1401 A pull request names the issue it realises, and the edge is derived rather than remembered
I1406 DONE 0 The plan's derivations are capabilities, so every surface reads the readiness the terminal reads
I1407 DONE 3 I1404, I1405, I1406 One issue travels the whole path and back, as an executable case
I1408 DONE 3 I1404 The documents stop claiming a projection nobody runs

The graph

work-graph-github dependency graphfrom .ai/repo/project
flowchart LR
    I1401["I1401<br/>The identity of a projected record is what the record carries, not what its title says"]:::done
    I1402["I1402<br/>A finding says which of six states it found, and the state that could hide a person's edit is no longer one of them"]:::done
    I1403["I1403<br/>Backfill the projection once, deliberately, and record what it created"]:::ready
    I1404["I1404<br/>The drift check is a gate, so a projection that stops being applied cannot stay silent"]:::done
    I1405["I1405<br/>A pull request names the issue it realises, and the edge is derived rather than remembered"]:::done
    I1406["I1406<br/>The plan's derivations are capabilities, so every surface reads the readiness the terminal reads"]:::done
    I1407["I1407<br/>One issue travels the whole path and back, as an executable case"]:::done
    I1408["I1408<br/>The documents stop claiming a projection nobody runs"]:::done
    I1401 --> I1402
    I1401 --> I1403
    I1401 --> I1405
    I1402 --> I1403
    I1402 --> I1404
    I1404 --> I1403
    I1404 --> I1407
    I1404 --> I1408
    I1405 --> I1407
    I1406 --> I1407
    classDef done stroke:#16a34a,fill:#052e16,stroke-width:2px
    classDef active stroke:#2563eb,fill:#eff6ff,stroke-width:2px
    classDef verify stroke:#7c3aed,fill:#f5f3ff,stroke-width:2px
    classDef ready stroke:#0891b2,fill:#ecfeff,stroke-width:2px
    classDef blocked stroke:#b45309,fill:#fffbeb,stroke-width:2px
    classDef cancelled stroke:#6b7280,fill:#f9fafb,stroke-width:2px

Canonical record: .ai/repo/project/milestones/work-graph-github.yaml. Read it back with majordomus plan show work-graph-github.