docs(server): forge self-origin notification suppression record (RIG-3326) - #997
Conversation
…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>
83bba66 to
ef1153e
Compare
|
😎 Stack merged successfully - details. |
…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>
|
Two additive commits pushed past the 2026-09-09 approval, so re-requesting review on the new head (
Worth noting because no per-PR signal shows it:
Union |
…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>
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.