Skip to content

feat(linearagent): Linear Agent Session webhook types + API client (RIG-2717 T1/T2) - #637

Merged
mattwilkinsonn merged 1 commit into
mainfrom
compass-server/rig-2717-linearagent
Aug 26, 2026
Merged

feat(linearagent): Linear Agent Session webhook types + API client (RIG-2717 T1/T2)#637
mattwilkinsonn merged 1 commit into
mainfrom
compass-server/rig-2717-linearagent

Conversation

@rigel-mintaka

@rigel-mintaka rigel-mintaka commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

This PR is part of a stack containing 3 PRs:

  1. main
  2. "feat(linearagent): Linear Agent Session webhook types + API client (RIG-2717 T1/T2)" (this PR)
  3. feat(linearagent): Linear Agent Session webhook types + API client (RIG-2717 T1/T2) #638
  4. feat(server): per-deployment public URL + deep-link builder (RIG-2717 T5) #639

New go/internal/linearagent package for the Linear Agent Session responder:

  • webhook.go (T1): SessionEvent envelope (AgentSessionEventWebhookPayload
    shape), ParseSessionEvent, VerifySignature (constant-time HMAC-SHA256 over
    the raw body, false on hex-decode error), CheckTimestamp (bidirectional
    skew on the ms-epoch webhookTimestamp).
  • client.go (T2): client-credentials TokenSource (in-memory cache,
    singleflight-coalesced re-mint, pinned scope), and the Client emitter
    wrapping agentActivityCreate (the thought ack) + agentSessionUpdate (the
    external-URL deep link), re-minting once and retrying once on 401.

Pure/unit-tested against httptest; no HTTP handler, store, or dispatcher yet
(sibling tasks). Builds against the frozen record
docs/designs/product/compass-linear-agent-responder/design.md.

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

@linear-code

linear-code Bot commented Aug 26, 2026

Copy link
Copy Markdown

RIG-2717

@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-server-rig-2717-line.compass-eng-docs.pages.dev

Deployed from compass-server/rig-2717-linearagent at 012b113.

…IG-2717 T1/T2)

New go/internal/linearagent package for the Linear Agent Session responder:

- webhook.go (T1): SessionEvent envelope (AgentSessionEventWebhookPayload
  shape), ParseSessionEvent, VerifySignature (constant-time HMAC-SHA256 over
  the raw body, false on hex-decode error), CheckTimestamp (bidirectional
  skew on the ms-epoch webhookTimestamp).
- client.go (T2): client-credentials TokenSource (in-memory cache,
  singleflight-coalesced re-mint, pinned scope), and the Client emitter
  wrapping agentActivityCreate (the thought ack) + agentSessionUpdate (the
  external-URL deep link), re-minting once and retrying once on 401.

Pure/unit-tested against httptest; no HTTP handler, store, or dispatcher yet
(sibling tasks). Builds against the frozen record
docs/designs/product/compass-linear-agent-responder/design.md.

Ref: RIG-2717
Co-authored-by: Matt Wilkinson <matt@rigel.build>
@rigel-mintaka
rigel-mintaka force-pushed the compass-server/rig-2717-linearagent branch from 15102f9 to 012b113 Compare August 26, 2026 04:01
rigel-mintaka added a commit that referenced this pull request Aug 26, 2026
…732)

Reworks the forge → agent notification pipeline (RIG-2732 Pieces 2 + 3) from a poll driver to a webhook-primary design, per Matt's transport-pivot ruling on this record.

## What changed from the poll draft

GitHub/Linear webhooks are the primary change signal, delivered to the `POST /webhooks` ingress whose shape RIG-2717 ratified (DL-254). A per-deployment GitHub App (folded into this record) supplies both the webhook subscription and the API credential via `forge.AppTokenSource` over the existing `TokenSource` seam. Reliability is a reconcile **catch-up sweep** at tens-of-minutes cadence (conditional-GET, diff-only) that heals gaps — not a poll-as-primary loop.

Honest framing: the sweep reuses ~80% of the poll draft's machinery (DetectChanges, snapshot codec, the conditional-read endpoints, the cursor table, per-target pacing, budget abort). What the rework deletes is the fast ticker, the reserve-floor, and the poll-as-primary role — the difference from a "webhooks + slow-poll fallback" hybrid is cadence-plus-intent.

## Design-critic pass folded

Ten findings from the red-team pass: six folded (the "ingress exists" premise corrected to shape-ruled-code-unlanded with explicit RIG-2717 sequencing; a cross-producer canonicalization invariant + meeting-point test; `check_suite.completed` as a roll-up *trigger* that fetches combined checks, not per-suite truth; honest hybrid framing + a justified 30-min backstop default; repo-scope PR-filtering + pagination walk; two unlanded-dependency markers as a Global Constraint), four surfaced into the load-bearing open questions below.

## Load-bearing open questions (freeze-gate decisions for Matt)

- **W1 — GitHub App token scope**: (a) unified credential [rec] / (b) split / (c) b-then-a read-only App now, defer the PAT→App write unification + `<app>[bot]` attribution flip to its own decision.
- **W2 — repo-scope wire encoding**: (a) `number=0` sentinel [rec on churn grounds] / (b) explicit scope enum — strengthened with the proto3 absent-vs-0 silent-misfire argument (a caught `InvalidArgument` bug becomes a silent whole-repo subscription).
- **W3 — delivered-revision advance signal**: (alpha) dispatch-success advance, bounded for *synchronous* refusal only [rec] / (beta') router.complete un-advance hook, closes the async subclass with no proto work / (beta) `delivery_ack` alignment with the frozen clause. Async refusal and post-dispatch loss are unbounded-until-next-change under alpha.
- **W4 — ingress-less deployments (NEW)**: a no-public-URL adopter (the class DL-053's poll-rejection protected) gets (a) no GitHub notifications, boot Warn [rec for this record] / (b) reconcile-only degraded mode / (c) hard-off now + (b) as a ratified follow-up.

## Ledger-impact

DL-N (webhook-primary transport + reconcile catch-up; supersedes the poll premise, cites DL-053's dissolved friction legs), DL-N+1 (ride the generic DispatchControl relay; reserved `forge_notification` variant superseded), DL-N+2 (delivery cursor advances on dispatch success — subclass-bounded, pending W3). Next-free id re-verified at freeze.

## Sequencing prerequisites (unlanded)

This record's tasks sequence after: the RIG-2717 impl stack (#637-639) + its later `/webhooks` mount task (ingress shape); PR #631 (Piece 1, forge subscription store writer) — open/blocked.

Ref: RIG-2732

Co-authored-by: Matt Wilkinson <matt@rigel.build>
@mattwilkinsonn
mattwilkinsonn merged commit 3217854 into main Aug 26, 2026
40 checks passed
@mattwilkinsonn
mattwilkinsonn deleted the compass-server/rig-2717-linearagent branch August 26, 2026 04:23
rigel-mintaka added a commit that referenced this pull request Aug 26, 2026
…732)

Reworks the forge → agent notification pipeline (RIG-2732 Pieces 2 + 3) from a poll driver to a webhook-primary design, per Matt's transport-pivot ruling on this record.

## What changed from the poll draft

GitHub/Linear webhooks are the primary change signal, delivered to the `POST /webhooks` ingress whose shape RIG-2717 ratified (DL-254). A per-deployment GitHub App (folded into this record) supplies both the webhook subscription and the API credential via `forge.AppTokenSource` over the existing `TokenSource` seam. Reliability is a reconcile **catch-up sweep** at tens-of-minutes cadence (conditional-GET, diff-only) that heals gaps — not a poll-as-primary loop.

Honest framing: the sweep reuses ~80% of the poll draft's machinery (DetectChanges, snapshot codec, the conditional-read endpoints, the cursor table, per-target pacing, budget abort). What the rework deletes is the fast ticker, the reserve-floor, and the poll-as-primary role — the difference from a "webhooks + slow-poll fallback" hybrid is cadence-plus-intent.

## Design-critic pass folded

Ten findings from the red-team pass: six folded (the "ingress exists" premise corrected to shape-ruled-code-unlanded with explicit RIG-2717 sequencing; a cross-producer canonicalization invariant + meeting-point test; `check_suite.completed` as a roll-up *trigger* that fetches combined checks, not per-suite truth; honest hybrid framing + a justified 30-min backstop default; repo-scope PR-filtering + pagination walk; two unlanded-dependency markers as a Global Constraint), four surfaced into the load-bearing open questions below.

## Load-bearing open questions (freeze-gate decisions for Matt)

- **W1 — GitHub App token scope**: (a) unified credential [rec] / (b) split / (c) b-then-a read-only App now, defer the PAT→App write unification + `<app>[bot]` attribution flip to its own decision.
- **W2 — repo-scope wire encoding**: (a) `number=0` sentinel [rec on churn grounds] / (b) explicit scope enum — strengthened with the proto3 absent-vs-0 silent-misfire argument (a caught `InvalidArgument` bug becomes a silent whole-repo subscription).
- **W3 — delivered-revision advance signal**: (alpha) dispatch-success advance, bounded for *synchronous* refusal only [rec] / (beta') router.complete un-advance hook, closes the async subclass with no proto work / (beta) `delivery_ack` alignment with the frozen clause. Async refusal and post-dispatch loss are unbounded-until-next-change under alpha.
- **W4 — ingress-less deployments (NEW)**: a no-public-URL adopter (the class DL-053's poll-rejection protected) gets (a) no GitHub notifications, boot Warn [rec for this record] / (b) reconcile-only degraded mode / (c) hard-off now + (b) as a ratified follow-up.

## Ledger-impact

DL-N (webhook-primary transport + reconcile catch-up; supersedes the poll premise, cites DL-053's dissolved friction legs), DL-N+1 (ride the generic DispatchControl relay; reserved `forge_notification` variant superseded), DL-N+2 (delivery cursor advances on dispatch success — subclass-bounded, pending W3). Next-free id re-verified at freeze.

## Sequencing prerequisites (unlanded)

This record's tasks sequence after: the RIG-2717 impl stack (#637-639) + its later `/webhooks` mount task (ingress shape); PR #631 (Piece 1, forge subscription store writer) — open/blocked.

Ref: RIG-2732

Co-authored-by: Matt Wilkinson <matt@rigel.build>
rigel-mintaka added a commit that referenced this pull request Aug 26, 2026
…732)

Reworks the forge → agent notification pipeline (RIG-2732 Pieces 2 + 3) from a poll driver to a webhook-primary design, per Matt's transport-pivot ruling on this record.

## What changed from the poll draft

GitHub/Linear webhooks are the primary change signal, delivered to the `POST /webhooks` ingress whose shape RIG-2717 ratified (DL-254). A per-deployment GitHub App (folded into this record) supplies both the webhook subscription and the API credential via `forge.AppTokenSource` over the existing `TokenSource` seam. Reliability is a reconcile **catch-up sweep** at tens-of-minutes cadence (conditional-GET, diff-only) that heals gaps — not a poll-as-primary loop.

Honest framing: the sweep reuses ~80% of the poll draft's machinery (DetectChanges, snapshot codec, the conditional-read endpoints, the cursor table, per-target pacing, budget abort). What the rework deletes is the fast ticker, the reserve-floor, and the poll-as-primary role — the difference from a "webhooks + slow-poll fallback" hybrid is cadence-plus-intent.

## Design-critic pass folded

Ten findings from the red-team pass: six folded (the "ingress exists" premise corrected to shape-ruled-code-unlanded with explicit RIG-2717 sequencing; a cross-producer canonicalization invariant + meeting-point test; `check_suite.completed` as a roll-up *trigger* that fetches combined checks, not per-suite truth; honest hybrid framing + a justified 30-min backstop default; repo-scope PR-filtering + pagination walk; two unlanded-dependency markers as a Global Constraint), four surfaced into the load-bearing open questions below.

## Load-bearing open questions (freeze-gate decisions for Matt)

- **W1 — GitHub App token scope**: (a) unified credential [rec] / (b) split / (c) b-then-a read-only App now, defer the PAT→App write unification + `<app>[bot]` attribution flip to its own decision.
- **W2 — repo-scope wire encoding**: (a) `number=0` sentinel [rec on churn grounds] / (b) explicit scope enum — strengthened with the proto3 absent-vs-0 silent-misfire argument (a caught `InvalidArgument` bug becomes a silent whole-repo subscription).
- **W3 — delivered-revision advance signal**: (alpha) dispatch-success advance, bounded for *synchronous* refusal only [rec] / (beta') router.complete un-advance hook, closes the async subclass with no proto work / (beta) `delivery_ack` alignment with the frozen clause. Async refusal and post-dispatch loss are unbounded-until-next-change under alpha.
- **W4 — ingress-less deployments (NEW)**: a no-public-URL adopter (the class DL-053's poll-rejection protected) gets (a) no GitHub notifications, boot Warn [rec for this record] / (b) reconcile-only degraded mode / (c) hard-off now + (b) as a ratified follow-up.

## Ledger-impact

DL-N (webhook-primary transport + reconcile catch-up; supersedes the poll premise, cites DL-053's dissolved friction legs), DL-N+1 (ride the generic DispatchControl relay; reserved `forge_notification` variant superseded), DL-N+2 (delivery cursor advances on dispatch success — subclass-bounded, pending W3). Next-free id re-verified at freeze.

## Sequencing prerequisites (unlanded)

This record's tasks sequence after: the RIG-2717 impl stack (#637-639) + its later `/webhooks` mount task (ingress shape); PR #631 (Piece 1, forge subscription store writer) — open/blocked.

Ref: RIG-2732

Co-authored-by: Matt Wilkinson <matt@rigel.build>
rigel-mintaka added a commit that referenced this pull request Aug 26, 2026
…732)

Reworks the forge → agent notification pipeline (RIG-2732 Pieces 2 + 3) from a poll driver to a webhook-primary design, per Matt's transport-pivot ruling on this record.

## What changed from the poll draft

GitHub/Linear webhooks are the primary change signal, delivered to the `POST /webhooks` ingress whose shape RIG-2717 ratified (DL-254). A per-deployment GitHub App (folded into this record) supplies both the webhook subscription and the API credential via `forge.AppTokenSource` over the existing `TokenSource` seam. Reliability is a reconcile **catch-up sweep** at tens-of-minutes cadence (conditional-GET, diff-only) that heals gaps — not a poll-as-primary loop.

Honest framing: the sweep reuses ~80% of the poll draft's machinery (DetectChanges, snapshot codec, the conditional-read endpoints, the cursor table, per-target pacing, budget abort). What the rework deletes is the fast ticker, the reserve-floor, and the poll-as-primary role — the difference from a "webhooks + slow-poll fallback" hybrid is cadence-plus-intent.

## Design-critic pass folded

Ten findings from the red-team pass: six folded (the "ingress exists" premise corrected to shape-ruled-code-unlanded with explicit RIG-2717 sequencing; a cross-producer canonicalization invariant + meeting-point test; `check_suite.completed` as a roll-up *trigger* that fetches combined checks, not per-suite truth; honest hybrid framing + a justified 30-min backstop default; repo-scope PR-filtering + pagination walk; two unlanded-dependency markers as a Global Constraint), four surfaced into the load-bearing open questions below.

## Load-bearing open questions (freeze-gate decisions for Matt)

- **W1 — GitHub App token scope**: (a) unified credential [rec] / (b) split / (c) b-then-a read-only App now, defer the PAT→App write unification + `<app>[bot]` attribution flip to its own decision.
- **W2 — repo-scope wire encoding**: (a) `number=0` sentinel [rec on churn grounds] / (b) explicit scope enum — strengthened with the proto3 absent-vs-0 silent-misfire argument (a caught `InvalidArgument` bug becomes a silent whole-repo subscription).
- **W3 — delivered-revision advance signal**: (alpha) dispatch-success advance, bounded for *synchronous* refusal only [rec] / (beta') router.complete un-advance hook, closes the async subclass with no proto work / (beta) `delivery_ack` alignment with the frozen clause. Async refusal and post-dispatch loss are unbounded-until-next-change under alpha.
- **W4 — ingress-less deployments (NEW)**: a no-public-URL adopter (the class DL-053's poll-rejection protected) gets (a) no GitHub notifications, boot Warn [rec for this record] / (b) reconcile-only degraded mode / (c) hard-off now + (b) as a ratified follow-up.

## Ledger-impact

DL-N (webhook-primary transport + reconcile catch-up; supersedes the poll premise, cites DL-053's dissolved friction legs), DL-N+1 (ride the generic DispatchControl relay; reserved `forge_notification` variant superseded), DL-N+2 (delivery cursor advances on dispatch success — subclass-bounded, pending W3). Next-free id re-verified at freeze.

## Sequencing prerequisites (unlanded)

This record's tasks sequence after: the RIG-2717 impl stack (#637-639) + its later `/webhooks` mount task (ingress shape); PR #631 (Piece 1, forge subscription store writer) — open/blocked.

Ref: RIG-2732

Co-authored-by: Matt Wilkinson <matt@rigel.build>
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.

2 participants