Skip to content

feat(mirror): mirror asks and topics to forge issues and PRs (RIG-5181) - #2275

Open
rigel-mintaka wants to merge 2 commits into
compass-server/rig-5171-board-derivefrom
compass-comms/rig-5181-ask-mirror
Open

rigel-mintaka wants to merge 2 commits into
compass-server/rig-5171-board-derivefrom
compass-comms/rig-5181-ask-mirror

Conversation

@rigel-mintaka

@rigel-mintaka rigel-mintaka commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

This PR is part of a stack containing 5 PRs:

  1. main
  2. feat(store): status board tables, patch merge and board reads (RIG-5170) #2212
  3. feat(comms): carry an optional issue or PR ref on asks (RIG-5173) #2228
  4. feat(board): derive board refs, PR gates and stacks from stored rows (RIG-5171) #2231
  5. "feat(mirror): mirror asks and topics to forge issues and PRs (RIG-5181)" (this PR)
  6. feat(board): agent board RPCs, roster summary and board events (RIG-5172) #2285

What

Mirrors asks, topics and message sets to forge issues and PRs, per §D6 and §T13 of docs/designs/agent/compass-agent-status-record/design.md.

  • Store. Adds the forge_mirror_settings and forge_mirrors tables in 0001_init.sql. Both have RLS (ENABLE and FORCE) and the updated_at trigger.
    • An agent ask whose ref names an Auto coordinate writes one source-1 row in the same transaction. An answer bumps that row's body_rev.
    • A leased outbox fences Begin and Mark on the token and on lease_until.
    • Mark leaves a row pending if body_rev moved on during the drive. A pending recheck_at survives later edits until the recheck scan clears it.
  • internal/mirror.
    • Render and RenderAsk produce bounded markdown that budgets for the marker. They neutralize quoted markers.
    • Drive/DriveOne lease, post or edit, and retry 3× (1 s, 4 s, 16 s).
    • A GitHub POST with a lost response is adopted on retry by its <!-- compass-mirror:<row> --> marker. A Linear POST is made idempotent by client_comment_id.
    • The GitHub recheck keeps the oldest marked comment and deletes newer duplicates. Only comments authored by the App's own login are ever adopted or deleted.
  • Server.
    • mirrorAllowed checks that the mode is at least what the post needs. It runs the agent forge scope check for agent actors.
    • PostMirror stamps the owner header for agents. A user click is attributed in the render header and carries no owner header.
    • Mirrors go to CommentOnPullRequest when a PR row exists, otherwise to CommentOnIssue. An existing key uses EditComment.
    • ListForgeMirrorSettings and SetForgeMirrorSetting: any user can list. Set requires the tenant's UserRoleAdmin and an enabled repo subscription.
    • Post-commit drives and recheck timers are bound to the server lifetime.
  • Comms.
    • askFromWire derives Ask.RefCoord, using the configured GitHub host for GHES.
    • PostMessage and RespondToAsk drive mirrors after commit, on a genuine insert only.
    • MirrorToForge is user-only. It checks channel visibility before the mode check and before any claim.
  • Forge. Adds App identity lookup: GitHub GET /app gives <slug>[bot]; Linear uses viewer.displayName. forge.MaxOwnerHeaderLen lets callers budget for the header before stamping.

Deferred to the board slice (ListBoardsAsAccount / UpdateBoardAsAccount land in RIG-5172): filling BoardAsk.mirror_state and mirror_url, and re-driving open rows on a board write. The proto fields ship now. The spec requirement for them waits for that slice.

Tests

  • pgtest internal/store: append Auto/Manual/Off/user/replay; answer bump; conflict keys; agent-class isolation on source keys; source-2 requires a key; lease, attempt and expired-lease fencing; open rows; revision bump during Mark; recheck preserved until cleared; mixed-case settings join; tenant isolation; RLS and trigger catalog floors.
  • pgtest internal/mirror: 17 Drive cases, among them lost-POST adoption after 60 comments, Linear client-id reuse, concurrent leases making one POST, recheck delete/repoint, foreign-marker immunity, and the stale-revision race. Render budget and neutralization.
  • pgtest server and internal/comms:
    • an auto ask posts once and its answer edits the same comment;
    • a mode turned off between claim and post marks the row failed;
    • a user click on a topic posts once, a second click edits it, and the post carries user attribution;
    • an invisible channel gives not-found; bad source combinations give invalid-argument;
    • the click wait is bounded by the request context;
    • settings enforce tenant admin;
    • MirrorToForge is user-only.
  • Unit tests: mode admission, body limit reserving the owner header, App identity required for recovery, recheck timer stopped on shutdown, GHES RefCoord, Linear identity field.

Spec-impact: docs/specs/product/compass.md (forge mirror settings, MirrorToForge, automatic ask mirroring)
Ledger-impact: none

Closes RIG-5181

Co-authored-by: Matt Wilkinson matt@rigel.build

Agent asks with a ref to an auto coordinate mirror once and edit on answer;
users mirror a visible ask, topic or message set by click. A leased outbox
with markers, client ids and a GitHub recheck keeps one comment per row.
Admins set off/manual/auto per enabled repo.

Co-authored-by: Matt Wilkinson <matt@rigel.build>
@linear-code

linear-code Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

RIG-5181

RIG-5171

@rigel-mintaka
rigel-mintaka changed the base branch from compass-comms/rig-5173-ask-ref to compass-server/rig-5171-board-derive October 13, 2026 09:24
@rigel-mintaka rigel-mintaka changed the title feat(board): derive board refs, PR gates and stacks from stored rows (RIG-5171) feat(mirror): mirror asks and topics to forge issues and PRs (RIG-5181) Oct 13, 2026
@github-actions

github-actions Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-comms-rig-5181-ask-m.compass-eng-docs.pages.dev

Deployed from compass-comms/rig-5181-ask-mirror at ba41f2b.

Changed pages:

- Mark the revision that was rendered, and re-drive while a newer one exists.
- Keep stored channel/topic names; take only the URL from the link.
- Back off failed rechecks, keep one timer per row, retry after lease loss.
- Cap topic mirrors at the newest 50 messages with a note (spec updated).
- Re-check channel membership after loading selected messages; canonicalize ids.
- Skip the agent lookup when a message has no mirrorable ask.

Co-authored-by: Matt Wilkinson <matt@rigel.build>
@rigel-mintaka

Copy link
Copy Markdown
Contributor Author

Review loop

Round Reviewer Findings Status
1 review-lead fan-out (correctness, design, security, style) 2 high, 9 medium, 3 low 7 fixed; 3 deferred (below); 2 style lows not taken
2 single reviewer on fix diff 5 medium all fixed
3 single reviewer on fix diff 1 medium fixed (lease-loss reread retry, regression test red then green)

Deferred by contract (design §T13 slice plan), not findings on this PR:

Not taken: the existing wireForgeWriteCaller comment length and its 12-parameter signature, which come from earlier code. Not worth churning in this PR.

Gates on this head: golangci-lint 0 issues (default and pgtest tags), nilaway clean, unit tests, pgtest internal/mirror, server, internal/comms, all ok.

@rigel-mintaka

Copy link
Copy Markdown
Contributor Author

Review round 4 on head ba41f2b99501

Scope: the fix for round 3. It covers:

  • the lease-loss reread fallback in scheduleMirrorRecheck;
  • the runLeaseLossRecheck test helper and TestForgeMirrorRecheckLeaseLossRetriesWhenRereadFails;
  • one blank line removed in the spec.

Result: 0 findings. The reviewer confirmed these:

  • The round-3 medium is fixed. A failed reread keeps the timer's row and retries with capped backoff.
  • There is no hot loop. A deleted row's next callback finds no RecheckAt and schedules nothing more.
  • Per-row timer coalescing and the WaitGroup accounting are unchanged.

Totals for rounds 1-4: 2 high, 15 medium and 3 low found. All the high and medium findings are fixed. Three items are deferred by contract, and two low style items were not taken (see the earlier note). CI is green on ba41f2b9.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant