Skip to content

feat(server): forge state-transition arms and actor memo (RIG-3331) - #1018

Draft
rigel-mintaka wants to merge 1 commit into
compass-forge/rig-3331-providerfrom
compass-forge/rig-3331-server-arms
Draft

feat(server): forge state-transition arms and actor memo (RIG-3331)#1018
rigel-mintaka wants to merge 1 commit into
compass-forge/rig-3331-providerfrom
compass-forge/rig-3331-server-arms

Conversation

@rigel-mintaka

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

Copy link
Copy Markdown
Contributor

Implements T4 of docs/designs/server/compass-forge-state-transition/design.md — the server arms and the actor memo. Stacked on #1017.

The arms

Two forgeService arms following the existing shape: resolveTarget, arm validation, author-client dispatch, mapForgeError flattening, updated canonical artifact through the existing translateIssue / translatePR.

Three validation screens, all before any provider touch:

  • State domainopen or closed; anything else (including merged and a Linear workflow-state name) is invalid_argument.
  • Refinement/providerclose_reason is GitHub-issues-only, workflow_state is Linear-only. Rejected HERE, keyed on the RESOLVED coordinate, which is why a provider may safely ignore a foreign refinement.
  • PR refinement — the PR arm accepts none at all.

No F3 dedup and no owner stamp on either arm: client_request_id is documented "ignored on non-create arms", and a transition has no body to stamp.

The memo (why it exists)

A transition has no body, so the owner header cannot attribute it, and every Server-credential write presents the shared App bot login. Durable server-side correlation is the only channel that can name WHICH agent drove a transition — that is what RIG-3331 OQ-1 ruled (2026-09-07), and it is what RIG-3326's STATE arm consumes.

forge_state_transitions lands in 0001_init.sql, not a new numbered migration: that directory holds exactly one migration by a standing ruling, and the same reasoning folds each later migration in as it accretes. It is coordinate-aligned to forge_authored_artifacts — same (tenant_id, forge_provider, forge_host, repo, kind, number) PK — so a re-transition re-lands on the key rather than accreting rows.

Load-bearing properties:

  • tenant_id is not incidental. Two tenants legitimately hold the same forge coordinate, so without it one tenant's memo could attribute another's STATE event. It rides the column DEFAULT + RLS, never a caller argument, matching the AuthoredArtifactByCoordinate precedent.
  • The consume is ONE statement (UPDATE … RETURNING), so claim and read are atomic and a concurrent second reader matches nothing.
  • The memo is written strictly AFTER provider success. Inverting it would attribute a STATE event to an agent whose write the forge refused.
  • A transition writes NO forge_authored_artifacts row — that row is write-once authorship whose DO UPDATE would destroy the original create's idempotency memo. Asserted explicitly.
  • The recorded state is the REQUESTED portable target, not the returned artifact's raw state: a merged PR reads back merged, outside both the notify lane's match domain and the table's CHECK.

One judgment call the record did not spell out

The upsert resets consumed_at to NULL. Without it, an agent that closes an issue (memo consumed) and later reopens it is permanently unattributable at that coordinate, contradicting "latest transition wins". It is behind its own named test so it can be reversed in one place.

Tests

13 unit tests in go/server over fake provider + fake store: dispatch on both arms, every validation rejection, memo written only after success, memo absent after failure, PR memo carries the portable state and PR kind. Includes a positive control — a screen that rejected EVERY refinement would pass the two rejection tests alone.

7 store pgtests, executed against real Postgres: upsert-latest-wins, upsert re-arms a consumed memo, consume-exactly-once, freshness bound (and its inclusivity), and one miss case per coordinate component.

The RLS coverage was proven load-bearing, not just written: removing the forge_state_transitions entry from the DO-loop turns TestRLSCatalogEnabledAndForced RED.

Ledger-impact: none — DL-342/DL-343 landed with the record's freeze in #981.

@linear-code

linear-code Bot commented Sep 8, 2026

Copy link
Copy Markdown

RIG-3331

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

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

Deployed from compass-forge/rig-3331-server-arms at 0b3dfaf.

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