docs(server): forge self-origin notification suppression record (RIG-3326) - #997
Open
rigel-mintaka wants to merge 2 commits into
Open
Conversation
rigel-mintaka
marked this pull request as ready for review
September 7, 2026 21:36
…3326)
Design record for suppressing self-origin forge notifications at the
notify-router fan-out: an agent no longer wakes on its own COMMENT / REVIEW /
OPENED / STATE on a subscribed artifact, matched on owner-qualified Compass
handles, failing open on any ambiguity. CHECKS is never suppressed. A suppressed
notification advances delivered_revision only when the subscriber was already
caught up (CAS-guarded, artifact-scope only), so the reconcile sweep neither
resurrects it nor masks a prior undelivered real notification.
The owner half of that identity is not on the wire today: AgentAttribution is
{agent_handle} only, so the parse discards the owner it already produces and the
COMMENT/REVIEW arm would be structurally inert. T0 restores owner_handle and
adds an internal ForgeEvent.Actor carrier for STATE (RIG-3331 populates it).
Sibling of Record A (forge self-delegate write path, PR #900); depends on
RIG-3331 (agent-driven state-transition write op) for the STATE arm to suppress
on the real transition actor rather than an author-row proxy.
Ledger-impact: adds DL-334 (self-origin notification suppression) and DL-335
(owner_handle carriage, amending the DL-094 clause DL-186 carried forward;
DL-186 stays Active).
Co-authored-by: Matt Wilkinson <matt@rigel.build>
resolve
|
Compass engineering docs preview: https://compass-forge-rig-3326-suppr.compass-eng-docs.pages.dev Deployed from Changed pages: |
…symbols not lines (RIG-3326) Two changes to the self-origin suppression record, both consequences of rulings that landed after it was frozen. 1. The STATE actor carrier is RIG-3331's memo, not a field on the event. This record froze the carrier as a new ForgeEvent.Actor field added by T0, on the reasoning that ForgeEvent has no actor slot and COMMENT/REVIEW are symmetric only because CommentRef holds theirs. Matt ruled the opposite on 2026-09-07 (RIG-3331 OQ-1): the actor travels in a durable, tenant-scoped forge_state_transitions memo written at the write-path chokepoint and consumed on match at the notify lane. A provider webhook is the forge's own statement of what happened, and stamping a Compass field onto the struct parsed from it invents an event the forge never sent. So T0 adds no ForgeEvent field. The field would have shipped inert: COMMENT/REVIEW resolve through CommentRef.Agent, OPENED through the DL-055 row, and STATE -- its only remaining client -- now resolves through the memo, leaving no populator and no consumer (rule://no-inert-gating). The fail-open interim is unchanged: a memo miss resolves no actor exactly as a nil field did, so STATE delivers until RIG-3331's op is writing memos. Amends the STATE row of the actor-source matrix, the carrier section, the T0 interface block, the T0 checklist entry, and DL-338's actor-source clause. Also states the dependency direction explicitly: RIG-3331 is the PREREQUISITE, this record is the consumer. 2. Symbol-plus-path citations. Replaces every path:line citation in the record and in DL-338/DL-339 with the symbol form. A line number is stale as soon as the file moves, and the drift is observable on main: PR #916 had to chase six prose citations in a merged record when resolver.go grew a few lines. All 104 were resolved to their enclosing symbol against the source tree and verified present in the cited file; the scrub was scoped to my own rows, leaving DL-064's citation into the vendored fork tree alone rather than resolving it by basename into an unrelated file. Refs RIG-3326 Co-authored-by: Matt Wilkinson <matt@rigel.build>
rigel-mintaka
force-pushed
the
compass-forge/rig-3326-suppression-design
branch
from
September 7, 2026 21:42
83bba66 to
ef1153e
Compare
rigel-mintaka
force-pushed
the
compass-forge/rig-3299-self-delegate-design
branch
from
September 7, 2026 21:42
f32510d to
8b28ed1
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.
This PR is part of a stack containing 2 PRs:
mainStacked PRs:
mainDesign record for suppressing self-origin forge notifications at the
notify-router fan-out: an agent no longer wakes on its own COMMENT / REVIEW /
OPENED / STATE on a subscribed artifact, matched on owner-qualified Compass
handles, failing open on any ambiguity. CHECKS is never suppressed. A suppressed
notification advances delivered_revision only when the subscriber was already
caught up (CAS-guarded, ARTIFACT-scope only), so the reconcile sweep neither
resurrects it nor masks a prior undelivered real notification.
Sibling of Record A (forge self-delegate write path, PR #900); depends on
RIG-3331 (agent-driven state-transition write op) for the STATE arm to suppress
on the real transition actor rather than an author-row proxy. That is a
dispatch-ordering dependency, not a freeze blocker — the STATE arm stays
interim-open until RIG-3331 lands.
Ledger-impact: adds DL-338 (self-origin notification suppression) and DL-339
(
compass.v1.AgentAttributionregainsowner_handle, field 2).Why DL-339
The review of record found the primary arm structurally inert: the owner half of
the owner-qualified handle never reaches the router.
AgentAttributioncarriesagent_handleonly, so all three CommentRef-build sites discard the owner thatStripOwneralready parses, leaving every COMMENT/REVIEW actor unqualified —which fails open, by design, and therefore never suppresses.
DL-094 dropped the field for a display consumer, and DL-186 carried that clause
forward while reclaiming the field numbers. Neither weighed an identity-matching
consumer: a bare handle is unique only per owner, so it cannot key a match.
Matt ruled to re-add the field. DL-339 amends the display-scoped clause and
leaves DL-186 Active, since its wire-compat-strip clause — the row's actual
subject — is untouched (the DL-308/DL-324 precedent).
T0 lands the proto field, populates it at the three parse sites, updates the UI
adapter that hardcodes
ownerHandle: "", and addsForgeEvent.Actoras theSTATE arm's carrier.
Review of record
Six rounds, one reviewer, every finding dispositioned — zero deferred, no
follow-up issues owed.
The instructive thread:
scopehad to traverse a hop chain across a mirroredtype boundary, and enumerating hops kept failing — each round named one more hop
and the next round found the one above it (the go/server adapter, then a second
ForgeNotifySubscriberproducer on the sweep lane). What terminated it was atype-quantified invariant plus an exhaustive constructor grep: prove the set is
closed rather than list its members.
Co-authored-by: Matt Wilkinson matt@rigel.build
Amendments in this push (post-freeze)
1. The STATE actor carrier is RIG-3331's memo, not a field on the event
This record froze the carrier as a new
ForgeEvent.Actorfield added by T0,reasoning that
ForgeEventhas no actor slot and COMMENT/REVIEW are symmetriconly because
CommentRefholds theirs. Matt ruled the opposite on2026-09-07 (RIG-3331 OQ-1): the actor travels in a durable, tenant-scoped
forge_state_transitionsmemo, written at the write-path chokepoint andconsumed on match at the notify lane. A provider webhook is the forge's own
statement of what happened; stamping a Compass field onto the struct parsed
from it invents an event the forge never sent.
So T0 adds no
ForgeEventfield, and the field would have shipped inert:COMMENT/REVIEW resolve through
CommentRef.Agent, OPENED through the DL-055row, and STATE — its only remaining client — now resolves through the memo,
leaving no populator and no consumer (
rule://no-inert-gating).The fail-open interim is unchanged: a memo miss resolves no actor exactly as a
nil field did, so STATE delivers until RIG-3331's op is writing memos. Amended
the STATE row of the actor-source matrix, the carrier section, the T0 interface
block, the T0 checklist entry, and DL-338's actor-source clause.
Dependency direction, stated explicitly: RIG-3331 is the PREREQUISITE and
this record is the consumer. RIG-3331's record is now frozen and merged
(#981,
Status: Active), so this record's STATE arm has its mechanism.2. Symbol-plus-path citations
Every
path:linecitation in the record and in DL-338/DL-339 now cites theenclosing symbol plus the file path. A line number is stale the moment the file
moves, and the drift is observable on main rather than hypothetical: PR #916
had to chase six prose citations in an already-merged record when
go/internal/secrets/resolver.gogrew a few lines.All 104 were resolved against the source tree and verified present in the
cited file. The scrub was scoped to my own ledger rows: DL-064's citation
into the vendored fork tree was left alone rather than resolved by basename,
which would have pointed it into an unrelated file in a 5,893-file subtree.
Verification
moon cigreen — 18 actions, 0 failed.design-ledger-gateOK (298 rows, 127record headers valid);
root:markdownlint0 errors. Design-record-only: no Go,proto, or SQL touched. Stack is linear (
main→ #900 → #997), as the queue'sstacked-merge path requires.