feat(forge): wire agent forge subscribe/unsubscribe — store writer + DL-053 GC (RIG-2732) - #631
Open
rigel-mintaka wants to merge 1 commit into
Open
feat(forge): wire agent forge subscribe/unsubscribe — store writer + DL-053 GC (RIG-2732)#631rigel-mintaka wants to merge 1 commit into
rigel-mintaka wants to merge 1 commit into
Conversation
|
Compass engineering docs preview: https://compass-server-rig-2732-forg.compass-eng-docs.pages.dev Deployed from |
…DL-053 GC (RIG-2732)
Lights up the two dormant forge subscription tools (`forge_subscribe` / `forge_unsubscribe`) at the store + server layer — the first piece of the forge agent-notification slice (RIG-2732). Both server arms previously returned `CodeUnimplemented` because `agent_forge_subscriptions` landed writer-less (poll-driver design, DL-163). This adds the writer and wires the arms.
### Store writer (`go/internal/store/forge_subscriptions.go`, new)
- `EnsureAgentForgeSubscription` — idempotent insert keyed by the `UNIQUE (agent_account_id, provider, host, repo, kind, number)`. A repeat subscribe by the same agent to the same artifact returns the EXISTING subscription id (the `ON CONFLICT … DO UPDATE` is a no-op touch that makes `RETURNING id` fire on the conflict path), never a duplicate row. An unknown agent (FK RESTRICT) → `ErrInvalidArgument`.
- `DeleteAgentForgeSubscription` — deletes by id scoped to the calling agent (`WHERE id = $1 AND agent_account_id = $2`); zero rows (unknown id, or an id owned by another agent) → `ErrNotFound`. Runs the **DL-053 garbage-collection invariant in one transaction**: after removing the subscription row, its coordinate's `forge_artifact_cursors` row is collected IFF that was the last subscription for the coordinate (a `NOT EXISTS` guard leaves the cursor in place while any other agent still subscribes). The coordinate comes from the deleted row's `RETURNING`.
- `AgentForgeSubscriptionsForArtifact` — a minimal row-count reader for the GC/idempotency assertions. NOT the poll driver's subscriber-enumeration reader (that is Piece 2).
This file never inserts a `forge_artifact_cursors` row — the poll driver owns that writer. The only cursor-table touch here is the last-subscription GC delete.
### Server arms (`go/server/forge.go`)
Wired the two `CodeUnimplemented` stubs: `subscribeForge` (resolve coordinate → map wire `ForgeArtifactKind` to the store enum, rejecting UNSPECIFIED as in-band `invalid_argument` → `EnsureAgentForgeSubscription` → `Subscribed{subscription_id}`) and `unsubscribeForge` (`DeleteAgentForgeSubscription` by id → `Unsubscribed`). A subscribe stamps no owner (it authors nothing). Store errors flow through the existing `storeForgeError` mapping (`ErrNotFound` → not_found, `ErrInvalidArgument` → invalid_argument).
### Tests (red-green)
- Store pgtests (`forge_subscriptions_pgtest_test.go`): idempotent subscribe (same id, one row); two agents on the same coordinate → two distinct ids; delete scoping (unknown id + foreign-agent id both `ErrNotFound`, owner delete succeeds); **DL-053 GC** (seed cursor + 2 agents → delete A leaves the cursor, delete B collects it); coordinate validation (zero provider / empty host / empty repo / zero kind / zero number → `ErrInvalidArgument`).
- Server default-lane tests (`forge_test.go`): subscribe returns an id + is idempotent; unspecified kind and empty repo → in-band `invalid_argument`; unsubscribe succeeds then not_found on repeat; a bogus id → in-band `not_found` (never a Connect teardown).
Pieces 2 (poll-driver change-detect → `ForgeNotification`) and 3 (turn-end delivery) follow as separate PRs; the wire path is already proto-provisioned (`AgentControl.forge_notification`, `SessionsResponse.forge_notification`).
Verified: `go build ./...`, `go vet`, `gofmt`, `compass-go:lint` (0 issues), and the affected `internal/store` / `server` suites all green (pgtests via the throwaway-container path).
Refs RIG-2732.
Co-authored-by: Matt Wilkinson <matt@rigel.build>
rigel-mintaka
force-pushed
the
compass-server/rig-2732-forge-sub-writer
branch
from
August 25, 2026 23:31
55be3c1 to
e371cf0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Lights up the two dormant forge subscription tools (
forge_subscribe/forge_unsubscribe) at the store + server layer — the first piece of the forge agent-notification slice (RIG-2732). Both server arms previously returnedCodeUnimplementedbecauseagent_forge_subscriptionslanded writer-less (poll-driver design, DL-163). This adds the writer and wires the arms.Store writer (
go/internal/store/forge_subscriptions.go, new)EnsureAgentForgeSubscription— idempotent insert keyed by theUNIQUE (agent_account_id, provider, host, repo, kind, number). A repeat subscribe by the same agent to the same artifact returns the EXISTING subscription id (theON CONFLICT … DO UPDATEis a no-op touch that makesRETURNING idfire on the conflict path), never a duplicate row. An unknown agent (FK RESTRICT) →ErrInvalidArgument.DeleteAgentForgeSubscription— deletes by id scoped to the calling agent (WHERE id = $1 AND agent_account_id = $2); zero rows (unknown id, or an id owned by another agent) →ErrNotFound. Runs the DL-053 garbage-collection invariant in one transaction: after removing the subscription row, its coordinate'sforge_artifact_cursorsrow is collected IFF that was the last subscription for the coordinate (aNOT EXISTSguard leaves the cursor in place while any other agent still subscribes). The coordinate comes from the deleted row'sRETURNING.AgentForgeSubscriptionsForArtifact— a minimal row-count reader for the GC/idempotency assertions. NOT the poll driver's subscriber-enumeration reader (that is Piece 2).This file never inserts a
forge_artifact_cursorsrow — the poll driver owns that writer. The only cursor-table touch here is the last-subscription GC delete.Server arms (
go/server/forge.go)Wired the two
CodeUnimplementedstubs:subscribeForge(resolve coordinate → map wireForgeArtifactKindto the store enum, rejecting UNSPECIFIED as in-bandinvalid_argument→EnsureAgentForgeSubscription→Subscribed{subscription_id}) andunsubscribeForge(DeleteAgentForgeSubscriptionby id →Unsubscribed). A subscribe stamps no owner (it authors nothing). Store errors flow through the existingstoreForgeErrormapping (ErrNotFound→ not_found,ErrInvalidArgument→ invalid_argument).Tests (red-green)
forge_subscriptions_pgtest_test.go): idempotent subscribe (same id, one row); two agents on the same coordinate → two distinct ids; delete scoping (unknown id + foreign-agent id bothErrNotFound, owner delete succeeds); DL-053 GC (seed cursor + 2 agents → delete A leaves the cursor, delete B collects it); coordinate validation (zero provider / empty host / empty repo / zero kind / zero number →ErrInvalidArgument).forge_test.go): subscribe returns an id + is idempotent; unspecified kind and empty repo → in-bandinvalid_argument; unsubscribe succeeds then not_found on repeat; a bogus id → in-bandnot_found(never a Connect teardown).Pieces 2 (poll-driver change-detect →
ForgeNotification) and 3 (turn-end delivery) follow as separate PRs; the wire path is already proto-provisioned (AgentControl.forge_notification,SessionsResponse.forge_notification).Verified:
go build ./...,go vet,gofmt,compass-go:lint(0 issues), and the affectedinternal/store/serversuites all green (pgtests via the throwaway-container path).Refs RIG-2732.
Co-authored-by: Matt Wilkinson matt@rigel.build