Own a story by its issue *and* PR assignees, and filter the board by owner (follow-up to #101)
@javiertoledo is already working on this.
Since Sep 1, 2026.
- Dominant language
- TypeScript
- Stars
- 71
- Forks
- 64
- Avg merge
- 15h 38m
- Merged PRs (30d)
- 66
Description
Follow-up to #101, in the same spirit as #174: the parts of "who owns this story" that #101's apps/web-only carve-out cannot reach.
#101 is being answered by #171 and #161 (and #175 for avatars). What those land is the issue's assignees on the row and header, plus a binary mine chip. Two things are outside that scope and outside every open PR.
The problem
1. A story's owner is not only the issue's assignees
A story is an issue and the pull requests that close it — assemblePipeline attaches them at services/api/src/pipeline.ts:241-258. Someone can be assigned on the PR without being assigned on the issue.
Facility does this itself. Triggering an agent assigns the requesting user on the issue and on the PR the run opens: services/api/src/sandbox/orchestrator.ts:2091 calls client.assignIssue(pr.number, githubLogin), and GitHub's issue-assignment API applies to pull requests too (services/api/src/github/client.ts:807). So Facility writes ownership onto the PR on every run and then has no way to read it back.
2. gh_pull_requests never mirrors assignees at all
There is no column. gh_issues.assignees exists at packages/db/src/schema.ts:239; the gh_pull_requests table (:261-306) has none. And pull-requests-sync.ts:442-449 maps title, draft, author, the refs and the CI fields — assignees are never read from the payload, on the webhook path or the backfill.
So #101's premise, "the data is already on the wire", holds for issues and is false for pull requests. This is the part that makes the union a schema and sync change rather than a rendering change, which is why it does not belong in #171.
The sharpest consequence is a PR that opened without a closing reference. It becomes a story in its own right, and services/api/src/pipeline.ts:272 hardcodes assignees: [] on it. That story is permanently unowned in the interface no matter who is assigned on GitHub — a hole that #171 and #161 render faithfully and cannot fix.
3. There is no filter by owner, only by me
#101 proposes one chip matching me.principal.githubLogin (services/api/src/routes/v1/shared.ts:123). The Stories board's only other filter is the stage chips (apps/web/app/(app)/projects/[projectId]/stories/page.tsx:101-133).
On a board where several people dispatch agents against the same repository, "what is X working on?" is the same question as "what am I working on?" and deserves the same answer. mine is one hard-coded case of a filter that should take any owner.
What I propose
Mirror PR assignees. Add assignees jsonb not null default '[]'::jsonb to gh_pull_requests (migration 0044, next in packages/db/migrations) and map the payload in pull-requests-sync.ts, reusing the shape issues-sync.ts:323 already established (login ?? "", blanks dropped). Set it in both the insert values and the onConflictDoUpdate set, or an unassignment will never clear.
The webhook path needs nothing new: processor.ts:207 calls upsertGhPullRequestFromWebhook on every pull_request delivery, so assigned and unassigned actions already arrive — they just carry a field nothing reads.
Union them into the story's owners. In assemblePipeline, derive owners: string[] as the issue's assignees plus every linked PR's, deduplicated case-insensitively, issue assignees first and GitHub's order preserved within each source, so the lead owner a row renders stays stable as PRs come and go. For the standalone PR story at :272, owners is simply the PR's own assignees.
I propose a new owners field rather than widening assignees, for two reasons: assignees means what its name says and apps/web/lib/delivery-intelligence.ts:274 consumes it with a different grammar on purpose; and it is additive, so #171 and #161 keep rendering the field they were written against and can adopt owners in a one-line change instead of being retargeted mid-review. Happy to be overruled — the alternative is that assignees on PipelineStorySchema silently changes meaning, which I would rather not do to three in-flight PRs.
Add owners to PipelineStorySchema (services/api/src/routes/v1/github.ts:225) and StoryDetailSchema (:267). PipelinePullRequestSchema (:192) gains assignees too, so the story detail can attribute an owner to the PR that carries them.
Filter the board by owner. Beside the stage chips, a control that takes any owner drawn from the visible pipeline, with me as the first entry resolved from me.principal.githubLogin. It filters on the union, composes with the stage chips rather than replacing them, and — like the stage chips, and unlike #104's search box — belongs in the URL, because "X's stories" is a view worth sending to someone. me should resolve to the login at render time rather than persisting it, so a shared link means the recipient's stories.
One behaviour to pin: when /v1/me fails or the principal has no GitHub login, me must be unavailable rather than silently matching nothing — the exact failure @adrian-lorenzo asked #171 to fix is worth not reintroducing here.
What I considered instead
- Widening
assigneesto mean the union. One less field, but it changes the meaning of a shipped wire field under three open PRs and underdelivery-intelligence, which wants the issue's own assignees. - Deriving PR owners from
author. Already mirrored, no migration. But the PR author is whoever opened it — for an agent run that is the app, not the person who dispatched it, andorchestrator.ts:2091assigns that person precisely because the author does not identify them. - A client-side owner filter over the fetched board, like #104/#150. Fine for a search box; wrong for a view you would share. The stage chips are links for the same reason.
- Doing this inside #101. Its three PRs are mid-review against an
apps/web-only scope; a migration and a sync change landing on top of them would strand the work rather than finish it.
Scope and testing
packages/db (migration + schema), services/api (sync, pipeline assembly, both response schemas), apps/web (owner rendering + the filter).
Per AGENTS.md, the sync change touches org- and project-scoped mirror rows, so it needs unit coverage on the assignee mapping (present, absent, blank login, unassignment clearing a previously-set list) and a deterministic integration test over a fake GitHub payload that covers the assigned/unassigned webhook actions and asserts a PR's owners never leak across org_id/project_id into another tenant's board.
Related: #101, #174, #104, #70, #18. Findings are against 8889753.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.