Skip to content

docs(server): forge self-origin notification suppression record (RIG-3326) - #997

Merged
trunk-io[bot] merged 6 commits into
compass-forge/rig-3299-self-delegate-designfrom
compass-forge/rig-3326-suppression-design
Sep 12, 2026
Merged

trunk-io[bot] merged 6 commits into
compass-forge/rig-3299-self-delegate-designfrom
compass-forge/rig-3326-suppression-design

Conversation

@rigel-mintaka

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

Copy link
Copy Markdown
Contributor

This PR is part of a stack containing 2 PRs:

  1. main
  2. docs(design): forge self-delegate write path record (Record A) (RIG-3299) #900
  3. "docs(server): forge self-origin notification suppression record (RIG-3326)" (this PR)

Supersedes #913 (same branch, same content). A rebase onto #900's updated
head rewrote this branch's commit, which detached #913 from its recorded head
and left it unreopenable. #913 never merged and carried no human review
comments; its body is preserved below with the amendments this push makes.
The rebase was needed because the stack had forked into a tree, which the
merge queue rejects at enqueue.


Stacked PRs:

  1. main
  2. docs(design): forge self-delegate write path record (Record A) (RIG-3299) #900
  3. "docs(server): forge self-origin notification suppression record (RIG-3326)" (this PR)

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.

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.AgentAttribution regains owner_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. AgentAttribution carries
agent_handle only, so all three CommentRef-build sites discard the owner that
StripOwner already 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 adds ForgeEvent.Actor as the
STATE arm's carrier.

Review of record

Six rounds, one reviewer, every finding dispositioned — zero deferred, no
follow-up issues owed.

Round Findings Outcome
1 3 high / 6 medium / 2 low H1 became the DL-339 fork above
2 0 / 3 / 3 all ten round-1 folds verified resolved
3 0 / 1 / 2 K=3 bound hit with a medium open; surfaced, exception granted
4 0 / 3 / 1 the round-3 finding's class recurred one layer up
5 0 / 1 / 3 enumeration proven complete by whole-tree sweep
6 0 / 0 / 1 all-clear; sole low cosmetic and fixed

The instructive thread: scope had to traverse a hop chain across a mirrored
type 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
ForgeNotifySubscriber producer on the sweep lane). What terminated it was a
type-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.Actor field added by T0,
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; stamping a Compass field onto the struct parsed
from it invents an event the forge never sent.

So T0 adds no ForgeEvent field, and 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. 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:line citation in the record and in DL-338/DL-339 now cites the
enclosing 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.go grew 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 ci green — 18 actions, 0 failed. design-ledger-gate OK (298 rows, 127
record headers valid); root:markdownlint 0 errors. Design-record-only: no Go,
proto, or SQL touched. Stack is linear (main#900#997), as the queue's
stacked-merge path requires.

@linear-code

linear-code Bot commented Sep 7, 2026

Copy link
Copy Markdown

RIG-3326

@rigel-mintaka
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
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-forge-rig-3326-suppr.compass-eng-docs.pages.dev

Deployed from compass-forge/rig-3326-suppression-design at cfd01ef.

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
rigel-mintaka force-pushed the compass-forge/rig-3326-suppression-design branch from 83bba66 to ef1153e Compare September 7, 2026 21:42
@trunk-io

trunk-io Bot commented Sep 8, 2026

Copy link
Copy Markdown

😎 Stack merged successfully - details.

rigel-mintaka and others added 3 commits September 9, 2026 21:22
…2 (RIG-3326)

main gained DL-338 from RIG-3490 after this branch claimed 338/339, so
design-ledger-gate:check failed on the merge-queue trial branch with a
duplicate ledger id. Move both rows to the free range above main's max.

Co-authored-by: Matt Wilkinson <matt@rigel.build>
…9 (RIG-3326)

Another lane claimed DL-351/352 on main (#1092, RIG-3655) while this record sat
in review, so the post-merge ledger carried two rows for each id. Main's max is
now DL-357; move to DL-358/359.

Co-authored-by: Matt Wilkinson <matt@rigel.build>
`apps/ui/e2e/.gitignore` ignored `/.output/` but not its sibling
`/.output-visual/`, so a local visual run's `.last-run.json` was snapshotted
into a commit and failed `root:lint` on formatting.

Co-authored-by: Matt Wilkinson <matt@rigel.build>
@rigel-mintaka

Copy link
Copy Markdown
Contributor Author

Two additive commits pushed past the 2026-09-09 approval, so re-requesting review on the new head (a83503e8).

fix(docs): ledger rows DL-351/352 → DL-358/359. #1092 (RIG-3655, user-secret store) claimed DL-351/352 on main while this record sat in review. Different rows, same ids — so the merged ledger carried two DL-351 and two DL-352 and would have bounced from the queue.

Worth noting because no per-PR signal shows it: git merge-tree --write-tree returns rc=0 with zero CONFLICT lines, and mergeable/reviewDecision/CI are all computed on the branch, not the merge result. The duplicate only exists in a tree that does not exist until the queue builds it. Caught by materializing the merged tree and running the gate on it (GATE_ROOT=<extracted> moon run design-ledger-gate:check) — 2 violations before, OK — 326 ledger row(s) after, re-verified against current main a5ad97e4.

fix(ui): ignore apps/ui/e2e/.output-visual/. apps/ui/e2e/.gitignore ignored /.output/ but not its sibling, so a stale local visual-run .last-run.json was snapshotted into a commit and failed root:lint on formatting. Fixed the ignore gap rather than deleting the file; jj file untrack had refused beforehand ("not ignored"), which was the actual diagnosis. root:lint 1 error → 0 (the 39 warnings are pre-existing on main).

Union moon ci 26/26, single ci run green, rollup success, no failed runs on the head. No content change to the record itself beyond the ledger ids.

…3326)

DL-358 was claimed on main by #1129 (RIG-3336, Compass Runner
containerization) while this record sat in review, and open #1164 claims
358 as well. Main's ledger max is DL-363, so 358/359 were both stale
reservations sized against an older main.

Renumbered to DL-364/DL-365, above main's max and free across every open
PR that touches DECISIONS.md.

Co-authored-by: Matt Wilkinson <matt@rigel.build>
@trunk-io
trunk-io Bot merged commit 5a4e346 into main Sep 12, 2026
15 checks passed
@trunk-io
trunk-io Bot deleted the compass-forge/rig-3326-suppression-design branch September 12, 2026 20:15
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