Skip to content

feat(forge): forge state-transition wire arms and provider methods (RIG-3331) - #1017

Open
rigel-mintaka wants to merge 1 commit into
mainfrom
compass-forge/rig-3331-provider
Open

feat(forge): forge state-transition wire arms and provider methods (RIG-3331)#1017
rigel-mintaka wants to merge 1 commit into
mainfrom
compass-forge/rig-3331-provider

Conversation

@rigel-mintaka

@rigel-mintaka rigel-mintaka commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

This PR is part of a stack containing 5 PRs:

  1. main
  2. "feat(forge): forge state-transition wire arms and provider methods (RIG-3331)" (this PR)
  3. feat(server): forge state-transition arms and actor memo (RIG-3331) #1018
  4. feat(agent): forge state-transition tools (RIG-3331) #1019
  5. feat(forge): carry the owner-qualified actor handle on the wire (RIG-3326) #1020
  6. feat(tools): mint the live-oracle Linear token with app:assignable (RIG-3299) #1021

Implements T0-T3 of the frozen record docs/designs/server/compass-forge-state-transition/design.md (Status: Active, merged in #981).

What this is

The wire arms and the provider half of the forge state-transition write op: an agent can move an issue or PR between forge states through the same chokepoint every other forge write goes through.

  • T0ForgeCallRequest oneof arms 14/15 + TransitionIssueStateRequest / TransitionPullRequestStateRequest, regenerated into both language lanes. The result arms are REUSED: a successful transition returns the updated artifact on the existing .issue / .pull_request arms, so a caller sees post-transition truth exactly as a create's caller sees the created artifact.
  • T1TransitionState input + Provider.TransitionIssueState / TransitionPullRequestState, landed together with all four implementors. The record is explicit that this is one slice: var _ Provider = ... assertions in fake.go, fake_test.go, github.go and linear.go mean widening the interface alone is red by construction.
  • T2 — GitHub PATCH /repos/{repo}/issues/{number} and /pulls/{number}, decoded through the existing ghIssue / ghPullDetail structs. The PATCH response IS the updated artifact, so nothing re-reads.
  • T3 — Linear issueUpdate with per-team workflow-state resolution, and ErrUnsupported on the PR method.

Two things a reviewer should look at

doJSON was POST-only. It now takes a method, with the five pre-existing callers passing http.MethodPost explicitly. A sibling helper would have duplicated the gate / token / Accept / budget / error-mapping body for one changed word. Behaviour for POST callers is unchanged; the one visible delta is the gate error prefix (%s %s, still POST <url> for every existing caller).

OQ-2's rule is implemented as ruled (2026-09-07): default to the sole candidate state of the target type, invalid_argument naming the candidates when more than one exists. The asymmetry is deliberate — close defaults to completed, never canceled. No current Rigel team has two completed states, so the multi-candidate rejection fixture is its only coverage; it exists for that reason.

Workflow-state resolution uses its OWN TTL cache with invalidate-and-retry-once, not the invalidation-free teamIDs cache. The retry fires only on a GraphQL-level rejection against a CACHED resolution — a rate limit, auth failure or transport fault never burns it.

All four rejection paths return *StatusError{422}, because mapForgeError reaches in-band invalid_argument by exactly that route; a plain fmt.Errorf would flatten to internal.

Tests

Golden replay (untagged) for both providers: GitHub close-with-reason / close-default / reopen / PR close / PR reopen / 422-on-merged-PR-reopen; Linear close-by-default / close-by-name / reopen-by-default / unknown-name / duplicate-name-in-team / type-contradiction / two-completed-states. Plus budget-gate, cache-TTL, retry-once and retry-not-on-rate-limit unit tests.

The fixture harness gained WantError because four Linear arms must FAIL and Want is a decoded domain value. Each rejection fixture asserts a request COUNT, so a rejection that fires before the wire — or after running the mutation — reddens.

Verified: go build -tags unix ./... clean, go test -tags unix ./internal/forge/ ok, go vet + gofmt clean.

Ledger-impact: none — this PR adds no ledger row. The record's DL-342/DL-343 landed with the freeze in #981.

Review round 1 — resolved

Reviewed by the review agent over the whole stack (high 3, medium 7, low 6).
The core RIG-3331 mechanism (memo ordering, one-shot consume, tenant isolation,
provider methods, Linear resolution, error mapping, recorded-state choice) was
verified correct. All three highs were stack-integration regressions, now fixed:

  • Stale base / three generated-file conflicts — rebased the line onto current
    main; every conflict resolved by regenerating (buf + sqlc), never by
    hand-merging a generated file.
  • Silent RIG-2616 revert — the stack's generated code predated main's
    SessionError regen (45 -> 0 occurrences). The regen restores it: SessionError
    is back to 45 in go/gen/compass/v1/compass.pb.go and 14 in the agent TS,
    with ownerHandle and the transition arms additive on top.
  • Deleted approval-mode assertions (feat(agent): forge state-transition tools (RIG-3331) #1019) — restored the full approvalOf
    loop over all twelve tools (3 reads + 9 writes) with its justification comment,
    rather than the two-tool assertion that replaced it.

Mediums fixed: updated_at/created_at + updated_at_tables entry for
forge_state_transitions (main's RIG-3495 convention, which landed after this
branched) plus a sqlc regen; the single-column FK divergence documented; the
memo coordinate contract documented on rememberTransition; the memo-failure
error now names the forge write that landed; the Linear retry gate narrowed to
the actual staleness signal; workflow-state page truncation now fails loud at
422; the two transition schemas routed through the compassv1 barrel.

Both new provider tests were mutation-proved: widening the retry gate reddens
TestLinearTransitionDoesNotRetryOnNonStaleness200, and removing the truncation
guard reddens TestLinearTransitionRejectsTruncatedWorkflowStatePage.

Gate: moon ci 70 actions, 0 failed against MOON_BASE=origin/main.

The initial forge live-oracle 401s were mint contention, not a defect: the
Linear client_credentials app holds one active token, so five concurrent CI
runs each revoked the previous one's (which is why the last-to-mint PR was
green). Re-run serially, the job passes on every head with no code change.

@linear-code

linear-code Bot commented Sep 8, 2026

Copy link
Copy Markdown

RIG-3331

@trunk-io

trunk-io Bot commented Sep 8, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-forge-rig-3331-provi.compass-eng-docs.pages.dev

Deployed from compass-forge/rig-3331-provider at f47fddd.

@rigel-mintaka
rigel-mintaka force-pushed the compass-forge/rig-3331-provider branch from daa4e13 to f47fddd Compare September 8, 2026 21:08
@rigel-mintaka
rigel-mintaka marked this pull request as ready for review September 8, 2026 21:55
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