From ca613b785f5b9ab52c6e3f56012f0673d9b4a11f Mon Sep 17 00:00:00 2001 From: seal Date: Thu, 6 Aug 2026 22:58:20 -0400 Subject: [PATCH 1/2] =?UTF-8?q?docs(product):=20correct=20forge=20=C2=A7A1?= =?UTF-8?q?=20proto=20placement=20to=20leaf=20forge.proto=20(SEA-1731)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Corrects the frozen `compass-server-ownership-layer-amendment` record so its §A1 add-list matches the shipped A1 proto (PR #149): the forge notification/carrier types (`ForgeNotification`/`ForgeNotificationKind`, `ForgeArtifactKind`, `CommentRef`) live in a new internal-only leaf `proto/compass/v1/forge.proto` that imports only `compass.proto`, **not** in `agent_gateway.proto` as #995 T1 and this record's original text directed — that placement is a circular import (`agent_gateway.proto` already imports `agent.proto`, so `agent.proto` referencing an `agent_gateway.proto` type is a cycle `buf` rejects). The `ForgeCall*` carrier + op requests stay in `agent_gateway.proto`; the leaf is imported acyclically by `agent.proto`/`agent_gateway.proto`/`runner.proto`. Wire contract, field numbers, and generated names are unchanged — placement only. Rewrites the "one structural consequence" section to "two structural consequences" (the cross-file import **plus** the new leaf), updates the T1 add-list and the row-13 disposition, and adds the ledger row for the placement decision. This correction was originally authored as a sealed PR (#1095) against the design corpus while it lived in `sealedsecurity/sealed`. #179 (SEA-1794) migrated the corpus + decision ledger + ledger gate into this repo, so the correction is re-homed here and #1095 is closed. The decision takes **DL-161** — allocated above the compass-ux-foundation reserved band that #200 landed on main (DL-148..160), so DL-161 is the next free id. Refs SEA-1731 Co-authored-by: Matt Wilkinson --- docs/designs/product/DECISIONS.md | 1 + .../design.md | 130 +++++++++++++----- 2 files changed, 95 insertions(+), 36 deletions(-) diff --git a/docs/designs/product/DECISIONS.md b/docs/designs/product/DECISIONS.md index 3279ff8c..e36ee7d6 100644 --- a/docs/designs/product/DECISIONS.md +++ b/docs/designs/product/DECISIONS.md @@ -137,6 +137,7 @@ check enforces the mechanical half. Full rationale: | DL-135 | Agent roster is a pull: a public `CommsService.GetRoster` (account-visibility-scoped via `accountVisibleFromWhere`, tree-derived scopes NEIGHBORHOOD/SUBTREE/OWNER — OWNER ratified fleet-wide under the shared-owner model, activity strings fleet-visible) plus a native `compass_roster` tool on the `CommsCallRequest` relay family, reading the DL-074 in-memory presence enum joined with the agent tree; the activity string is DURABLE (its own `agent_activity` store table), recovered from Postgres on Server restart — agent-set via `compass_set_status` (write-through to the table + additive `AgentPresenceChanged.activity`), re-published by the agent-side harness on session (re)attach for freshness — a deliberate divergence from DL-074's in-memory posture for the STRING; the presence enum stays in-memory. Presence EVENTS keep the shared-channel visibility rule while the roster uses account visibility (divergence stated: an owner sharing no channel with its agent gets snapshot-only presence). SEA-1721 | Active (Matt, 2026-07-31) | [manager comms substrate](compass-manager-comms-substrate/design.md#a1--sea-1721-roster-query--a-getroster-rpc--compass_roster-tool-over-the-d4-presence-snapshot) | | DL-136 | Channel post authority and forced subscription are channel-level policy fields (`post_policy` OPEN/OWNER_ONLY + `owner_account_id` + `mandatory_subscription`), never a `ChannelKind` or a visibility overload; `mandatory_subscription` is enforced read-side as a third disjunct in D1's subscriber resolution; an owner/operator `SetChannelPolicy` (create-or-update) is the only post-creation mutation path, its txn seeding delivery cursors for every member a mandatory flip newly targets. A Manager's coordination channel is auto-provisioned on first report gained via a reconciler hooked at the two store-level writers of `agent_accounts.parent_agent_id` (`store.CreateAgent`, `store.ReparentAgent`) — covering spawn, public `CreateAgent`, reparent, and any future writer by construction; resume on name-collision is ownership-checked (never adopts a user-created channel); membership reconciles with tree edges; dormant channels are never deleted (accepted MVP accretion). The coordination channel is OWNER_ONLY + mandatory-subscribe — a one-way manager→reports directive surface; report→manager and lateral coordination flows through DMs/group DMs, and agents at every level heavily prefer direct DMs/small targeted group DMs to keep coordination-token-cost low (standing directive, Matt). Precursor primitive to SEA-1622, not part of it. SEA-1722 | Active (Matt, 2026-07-31) | [manager comms substrate](compass-manager-comms-substrate/design.md#a2--sea-1722-coordination-channel--a-channel-level-post_policy--mandatory_subscription-auto-provisioned-from-tree-edges) | | DL-137 | The pinned board is a server-side per-channel capped ordered POINTER set over existing topic-scoped messages: pinning references an existing `message_id` (validated to a topic of the channel, join through `topics`) and creates no `Message`, so DL-099's single-write-path stands; edit = a normal topic-mandatory post + compare-and-swap repoint (redelivery is ordinary DL-071/072/073 delivery of a new message id, dedup-safe); board txns serialize on `channels … FOR UPDATE` (cap + repoint races); startup delivery is a session-start pin sweep beside the D2 cursor sweep dispatching current pins regardless of cursor position; edits land at turn-settle (never steer); explicitly distinct from DL-096's client-local sidebar pins, and compatible with DL-054 (no centre/badge/read state). SEA-1723 | Active (Matt, 2026-07-31) | [manager comms substrate](compass-manager-comms-substrate/design.md#a3--sea-1723-pinned-board--a-pure-pointer-set-over-existing-topic-scoped-messages-edit--topic-mandatory-post--repoint-so-redelivery-is-delivery) | +| DL-161 | The forge notification/carrier types that `AgentControl.forge_notification` reaches (`ForgeNotification`/`ForgeNotificationKind`, `ForgeArtifactKind`, `CommentRef`) live in a new internal-only leaf `proto/compass/v1/forge.proto` (imports only `compass.proto`), NOT in `agent_gateway.proto` as #995 T1 / this record's original §A1 add-list directed: that placement is a circular import (`agent_gateway.proto` already imports `agent.proto`, so `agent.proto` referencing an `agent_gateway.proto` type is a cycle buf rejects). The `ForgeCall*` carrier + op requests stay in `agent_gateway.proto`; the leaf is imported by `agent.proto`/`agent_gateway.proto`/`runner.proto` acyclically. Wire contract, field numbers, and generated names unchanged — placement only | Active (Matt, 2026-08-05) | [ownership amendment §A1](compass-server-ownership-layer-amendment/design.md#a1--proto-amend-the-t1-add-list-to-the-reconciled-surface) | ## Agent roles & prompts diff --git a/docs/designs/product/compass-server-ownership-layer-amendment/design.md b/docs/designs/product/compass-server-ownership-layer-amendment/design.md index 3c90c3bf..6432854c 100644 --- a/docs/designs/product/compass-server-ownership-layer-amendment/design.md +++ b/docs/designs/product/compass-server-ownership-layer-amendment/design.md @@ -110,7 +110,7 @@ amendment: | 10 | The seven op requests (#995 :433-473): `CreateIssueRequest`, `CommentOnIssueRequest`, `GetIssueRequest`, `ListIssuesRequest`, `CreatePullRequestRequest`, `CommentOnPullRequestRequest`, `GetPullRequestRequest` | **SURVIVE** | Confirmed per-request this run: every field in all seven is a scalar (`string`/`uint64`/`uint32`/`bool`/`repeated string`) — no forge domain type appears in any request shape (#995 :433-473). Read-op requests (`GetIssueRequest`, `ListIssuesRequest`, `GetPullRequestRequest`) survive (OQ-A ruled option 3 — answered from the projection/store, §Resolved decisions). One width note: #995 keeps `uint64` issue/pull numbers in these requests (:444-446, :470-472) while the canonical result types carry `uint32 number` (#1018 :178); narrow the surviving request fields to `uint32` to match the canonical width — free now, buf-breaking after the surface ships. | | 11 | `ListIssuesResponse` (#995 :454-456) | **RECONCILED** | `repeated Issue issues = 1` retypes to `repeated compass.v1.Issue`; the read op is answered from the projection/store (OQ-A ruled option 3). | | 12 | `SubscribeForgeRequest`/`Response`, `UnsubscribeForgeRequest`/`Response`, `ForgeArtifactKind` (#995 :1027-1043) | **SURVIVE** | All scalar/enum fields (`repo`, `kind`, `number`, `subscription_id`); no domain type. | -| 13 | `ForgeNotification`/`ForgeNotificationKind` (#995 :1191-1212) | **RECONCILED** | The message and kind enum survive; two arms retype: `ForgeChecksSummary checks = 9` → `compass.v1.ChecksSummary`; `IssueComment comment = 8` → the ruled `CommentRef` (§Resolved decisions OQ-B). Its `string provider = 2` (#995 :1193) stays a bare string — internal-only and tolerable, but a second forge-identity vocabulary vs `compass.v1.ForgeRef`; retype to `ForgeRef` in the same pass if the notification path should carry one vocabulary. | +| 13 | `ForgeNotification`/`ForgeNotificationKind` (#995 :1191-1212) | **RECONCILED + RELOCATED (DL-161)** | The message and kind enum survive but move to the new leaf `forge.proto` (with `ForgeArtifactKind` + `CommentRef`) to break the notification-placement cycle. Three arms reconcile: `ForgeChecksSummary checks = 9` → `compass.v1.ChecksSummary`; `IssueComment comment = 8` → the ruled `CommentRef` (§Resolved decisions OQ-B); and `string provider = 2` (#995 :1193) is retyped to `compass.v1.ForgeRef forge = 2` — the shipped decision (forge.proto `ForgeRef forge = 2`), so the notification path carries the one `compass.v1` forge-identity vocabulary rather than a second bare-string one. | | 14 | `runner.proto`: `RelayForgeCall` + `RelayForgeCallRequest`/`Response` (#995 :485-493) | **SURVIVES** | Pure wrapper: `ForgeCallRequest call` / `ForgeCallResult result` by reference; reconciliation is inherited from items 2-3. | | 15 | `SessionsResponse.forge_notification = 7` (#995 :1633) | **SURVIVES** | Carries `ForgeNotification`, reconciled internally (item 13). | | 16 | `agent.proto`: `AgentControl.forge_notification = 9` (#995 :1633-1635) | **SURVIVES** | Same carrier logic as item 15. | @@ -132,19 +132,33 @@ unambiguous); adding an optional `ForgeRef`/provider to the op requests and `SubscribeForgeRequest` is a named additive follow-up, free to add later without a buf-breaking change. -### One structural consequence: a cross-file import - -The #995 record placed the domain messages in `agent_gateway.proto` -(internal-only). The canonical family lives in `compass.proto` — public, -package `compass.v1` (#1018 design.md:99-101). Retyping the result arms -therefore makes `agent_gateway.proto` import `compass/v1/compass.proto`. This -edge is already present transitively (`agent_gateway.proto` pulls `agent.proto` -which imports `compass.proto`, `agent.proto:25`); making it direct changes no -gen wiring the lanes don't already handle. The two internal gen lanes treat -the canonical types differently — the TS lane regenerates them via -`--include-imports`, the Go lane M-redirects them to the public `go/gen` -package — and the gen-fence must keep the canonical symbols unfenced. Detail -in Global Constraints below. +### Two structural consequences: a cross-file import and a new leaf file + +**(1) The carrier imports the canonical family.** The #995 record placed the +domain messages in `agent_gateway.proto` (internal-only). The canonical family +lives in `compass.proto` — public, package `compass.v1` (#1018 +design.md:99-101). Retyping the result arms therefore makes +`agent_gateway.proto` import `compass/v1/compass.proto`. This edge is already +present transitively (`agent_gateway.proto` pulls `agent.proto` which imports +`compass.proto`, `agent.proto:25`); making it direct changes no gen wiring the +lanes don't already handle. The two internal gen lanes treat the canonical +types differently — the TS lane regenerates them via `--include-imports`, the Go +lane M-redirects them to the public `go/gen` package — and the gen-fence must +keep the canonical symbols unfenced. + +**(2) A new internal leaf `forge.proto` (DL-161).** The types +`AgentControl.forge_notification` reaches (`ForgeNotification`/ +`ForgeNotificationKind`, `ForgeArtifactKind`, `CommentRef`) do NOT live in +`agent_gateway.proto` as #995 T1 first directed — that placement is a circular +import, since `agent_gateway.proto` already imports `agent.proto` and +`agent.proto` would then have to import back for the notification type. They +move into a new internal-only leaf `proto/compass/v1/forge.proto` that imports +only `compass.proto`; `agent.proto`, `agent_gateway.proto`, and `runner.proto` +all import the leaf without a cycle. This adds one internal file to all three +gen lanes (buf.gen.yaml exclude + both internal-lane inputs) and to the +gen-fence symbol set. Wire contract, field numbers, and generated names are +unchanged — only the source file each type lives in moves. Detail in Global +Constraints below. ## Alternatives considered @@ -197,13 +211,19 @@ amended proto work, confirmed against the live tree this run. task (`buf breaking … --against origin/main`, #995 design.md:1490-1491) with no new exemption. Nothing is ever removed from a shipped wire surface. - **Files touched** (amending #995 T1's list, design.md:1625-1639): - `proto/compass/v1/agent_gateway.proto` (carrier family + reconciled arms), - `proto/compass/v1/runner.proto` (`RelayForgeCall`, - `SessionsResponse.forge_notification = 7`), `proto/compass/v1/agent.proto` - (`AgentControl.forge_notification = 9`), and the `gen-fence` grep in - `proto/moon.yml`. Note a line-drift against #995's cite: the gen-fence grep - script now sits at `proto/moon.yml:151` (task `gen-fence` opens at :121), - not :141 as #995 recorded — confirmed by reading the live file this run. + the NEW leaf `proto/compass/v1/forge.proto` (`ForgeNotification`/ + `ForgeNotificationKind`, `ForgeArtifactKind`, `CommentRef`; imports only + `compass.proto` — DL-161), `proto/compass/v1/agent_gateway.proto` (carrier + family + reconciled arms; imports the leaf), `proto/compass/v1/runner.proto` + (`RelayForgeCall`, `SessionsResponse.forge_notification = 7`; imports the + leaf), `proto/compass/v1/agent.proto` (`AgentControl.forge_notification = 9`; + imports the leaf), the `gen-fence` grep in `proto/moon.yml`, and the three gen + templates the new leaf is wired into — `buf.gen.yaml` (added to + `exclude_paths`), `buf.gen.internal-go.yaml` (added to `paths` + `M`-maps), + and `buf.gen.agent-ts.yaml` (added to `paths`). Note a line-drift against + #995's cite: the gen-fence grep script sits at `proto/moon.yml:151` (task + `gen-fence` opens at :121), not :141 as #995 recorded — confirmed by reading + the live file this run. - **The gen-fence extension fences the carrier family only.** Extend the grep with the unanchored `ForgeCall|RelayForgeCall|ForgeNotification| ForgeArtifactKind` family exactly as #995 T1 specifies (design.md:1636-1639) @@ -232,7 +252,13 @@ amended proto work, confirmed against the live tree this run. deliberately M-maps `compass.proto` to the PUBLIC `go/gen` package and MUST NOT gain `--include-imports` — its header warns that doing so triggers a duplicate-registration init panic. A1 confirms this and that - `buf.gen.yaml`'s `exclude_paths` still excludes every internal file. + `buf.gen.yaml`'s `exclude_paths` still excludes every internal file. The + placement correction (DL-161) adds one more internal file to keep in that + fence: the new `proto/compass/v1/forge.proto` leaf is added to + `buf.gen.yaml`'s `exclude_paths`, and to the `paths` inputs of both internal + lanes (`buf.gen.internal-go.yaml` with its `M`-maps → `compassv1internal`, + and `buf.gen.agent-ts.yaml`). forge.proto imports only `compass.proto`, so it + introduces no new cross-package edge beyond the one described here. - **Enum sentinel rule.** Any surviving new enum (`ForgeArtifactKind`, `ForgeNotificationKind`) keeps its `_UNSPECIFIED = 0` sentinel: the `ENUM_ZERO_VALUE_SUFFIX` exemption covers `comms.proto` only (#995 @@ -253,12 +279,31 @@ task briefs below reflect those rulings. ### A1 — Proto: amend the T1 add-list to the reconciled surface -Amends #995 T1 (design.md:1621-1651). Add to +Amends #995 T1 (design.md:1621-1651). **Placement corrected (Matt, 2026-08-05):** #995 T1 +and this record's original draft directed `ForgeNotification` into +`agent_gateway.proto` while also adding `AgentControl.forge_notification` to +`agent.proto`. That is a **circular import** — `agent_gateway.proto` already +imports `agent.proto` (for `AgentFrame`/`AgentControl`), so a back-reference +from `agent.proto` to a type defined in `agent_gateway.proto` is a cycle buf +rejects (`buf lint`: `compass/v1/agent.proto: detected cyclic import while +importing "compass/v1/agent_gateway.proto"`). Every other `AgentControl` +payload type is defined in `agent.proto`; forge is the first `agent_gateway.proto` +call family that also needs an `AgentControl` notification variant, which is why +the defect surfaced only here. The fix is a new internal-only leaf +`proto/compass/v1/forge.proto` that imports **only** `compass/v1/compass.proto` +and holds every type reachable from `AgentControl.forge_notification` +(`ForgeNotification`, `ForgeNotificationKind`, `ForgeArtifactKind`, and the +`CommentRef` reference); `agent.proto`, `agent_gateway.proto`, and `runner.proto` +all import the leaf without a cycle. The wire contract, field numbers, and +generated Go/TS names are identical to the shapes below — only the source file +each type lives in changes (DL-161). + +Add to `proto/compass/v1/forge.proto` (new leaf): `ForgeNotification` / +`ForgeNotificationKind`, `ForgeArtifactKind`, and `CommentRef`. Add to `proto/compass/v1/agent_gateway.proto`: the `Forge` RPC; `ForgeCallRequest`/`ForgeCallResult`/`ForgeCallError`; the seven operation requests and `ListIssuesResponse`; `SubscribeForgeRequest`/`Response`, -`UnsubscribeForgeRequest`/`Response`, `ForgeArtifactKind`; -`ForgeNotification`/`ForgeNotificationKind`. Do **NOT** add the six domain +`UnsubscribeForgeRequest`/`Response`. Do **NOT** add the six domain messages (`Issue`, `IssueComment`, `PullRequest`, `ForgeAuthor`, `ForgeChecksSummary`, `ForgeCheck`) — instead import `compass/v1/compass.proto` and retype the carrier arms: @@ -269,19 +314,26 @@ messages (`Issue`, `IssueComment`, `PullRequest`, `ForgeAuthor`, get_issue), `ListIssuesResponse issues = 4`, `compass.v1.PullRequest pull_request = 5` (create_pull_request / get_pull_request); the two former `IssueComment` arms (`issue_comment = 3`, `pr_comment = 6`) take the ruled - `CommentRef` (§Resolved decisions OQ-B) — a write ack sets `url` + - `comment_id`. + `CommentRef` (§Resolved decisions OQ-B; defined in the `forge.proto` leaf) — a + write ack sets `url` + `comment_id`. - `ListIssuesResponse { repeated compass.v1.Issue issues = 1; }` -- `ForgeNotification`: `compass.v1.ChecksSummary checks = 9`; `comment = 8` - takes the same ruled `CommentRef`, with `body` + `forge_account` + (for a - Compass-agent commenter) `agent` set. +- `ForgeNotification` (in `forge.proto`): `compass.v1.ChecksSummary checks = 9`; + `comment = 8` takes the same ruled `CommentRef`, with `body` + `forge_account` + + (for a Compass-agent commenter) `agent` set. Its `forge` field is a + `compass.v1.ForgeRef` (one forge-identity vocabulary, §item 13), not a bare + `string provider`. - `runner.proto`: `rpc RelayForgeCall(RelayForgeCallRequest) returns (RelayForgeCallResponse)` + `SessionsResponse.forge_notification = 7`; - `agent.proto`: `AgentControl.forge_notification = 9` — all verbatim from - #995 T1 (design.md:1631-1635). -- `gen-fence` grep (`proto/moon.yml:151`) extended with unanchored - `ForgeCall|RelayForgeCall|ForgeNotification|ForgeArtifactKind`; canonical - `compass.v1` symbols NOT fenced. + `agent.proto`: `AgentControl.forge_notification = 9` (its type + `ForgeNotification` resolved through the `forge.proto` import) — field numbers + verbatim from #995 T1 (design.md:1631-1635). +- `gen-fence` grep (`proto/moon.yml`) extended with unanchored + `ForgeCall|RelayForgeCall|ForgeNotification|ForgeArtifactKind` plus the + word-boundable internal-only requests (`CommentRef`, the op requests, + `SubscribeForge*`/`UnsubscribeForge*`); canonical `compass.v1` symbols NOT + fenced. The new `forge.proto` is wired into all three gen lanes: the public + `buf.gen.yaml` exclude, the `buf.gen.internal-go.yaml` inputs + `M`-maps + (→ `compassv1internal`), and the `buf.gen.agent-ts.yaml` inputs. - Read ops survive (OQ-A ruled option 3): `GetIssueRequest`, `ListIssuesRequest`, `GetPullRequestRequest`, `ListIssuesResponse`, and the `get_issue`/`list_issues`/`get_pull_request` oneof members + the `issues` @@ -391,11 +443,17 @@ renumbered the duplicate DL-071 to DL-091) the highest allocated id is DL-091 (DECISIONS.md:129), DL-082..089 are reserved for #1021 (SEA-1570, in flight), and DL-056..066 is an older unexplained gap. The next free id above the reserved band is **DL-092**, which this amendment proposes. (The single-writer -owns the final id; only the id cell changes.) +owns the final id; only the id cell changes.) The placement-correction row +**DL-161** was allocated later, at correction time: after the design corpus +migrated into this repo (#179), it takes the next free id above the whole +reserved band (the compass-ux-foundation record #186 reserves DL-148..160), so +it clears every declared block — ledger ids are non-sequential by convention, +grouped by theme not issued in order. | ID | Decision | Status | Record | | --- | --- | --- | --- | | DL-092 | #995's forge-shaped proto domain family (`Issue`/`IssueComment`/`PullRequest`/`ForgeAuthor`/`ForgeChecksSummary`/`ForgeCheck` in `agent_gateway.proto`) is not built: per DL-069 the forge Provider layer's value types stay internal Go data at the ingestion boundary, the `ForgeCall*` carrier family survives with its result arms retyped to the canonical `compass.v1` types, and `AgentAttribution` supersedes `ForgeAuthor` (closing #995 OQ-1's field-set gate) | Active (Matt, 2026-07-31) | [ownership amendment §What this amendment asserts](compass-server-ownership-layer-amendment/design.md#what-this-amendment-asserts) | +| DL-161 | The forge notification/carrier types that `AgentControl.forge_notification` reaches (`ForgeNotification`/`ForgeNotificationKind`, `ForgeArtifactKind`, `CommentRef`) live in a new internal-only leaf `proto/compass/v1/forge.proto` (imports only `compass.proto`), NOT in `agent_gateway.proto` as #995 T1 / this record's original §A1 add-list directed: that placement is a circular import (`agent_gateway.proto` already imports `agent.proto`, so `agent.proto` referencing an `agent_gateway.proto` type is a cycle buf rejects). The `ForgeCall*` carrier + op requests stay in `agent_gateway.proto`; the leaf is imported by `agent.proto`/`agent_gateway.proto`/`runner.proto` acyclically. Wire contract, field numbers, and generated names unchanged — placement only | Active (Matt, 2026-08-05) | [ownership amendment §A1](compass-server-ownership-layer-amendment/design.md#a1--proto-amend-the-t1-add-list-to-the-reconciled-surface) | No existing #995 row flips: DL-048 (ownership layer), DL-049 (`ForgeCall*` carrier on the gateway socket), DL-050 (owner header), DL-051 (`go/internal/ From 8077ee273be87a60619d4b37c91432e602ee9891 Mon Sep 17 00:00:00 2001 From: seal Date: Fri, 7 Aug 2026 12:58:23 -0400 Subject: [PATCH 2/2] =?UTF-8?q?docs(product):=20reconcile=20gen-fence=20sp?= =?UTF-8?q?ec=20in=20forge=20=C2=A7A1=20amendment=20record?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review of #199 flagged the frozen amendment record contradicting itself on the gen-fence grep contents after the A1-brief correction: the A1 brief made the fence firm (carrier family + internal-only requests + CommentRef) while the Global Constraints bullet still said "fences the carrier family only" and framed the requests as an open choice ("or accept the gap explicitly"). This matters functionally — DL-161 relocates CommentRef into the internal-only forge.proto leaf, so it MUST be fenced off the public gen surface; an implementer following "carrier family only" would under-fence a now-internal symbol. - Global Constraints: retitle the bullet to "fences the carrier family plus the internal-only requests" and state the decision (the fence IS extended with the word-boundable internal-only names, CommentRef fenced because DL-161 makes it internal) instead of leaving it an accepted gap. - Survival-table row 12: note ForgeArtifactKind relocates to the forge.proto leaf per DL-161 (see row 13) at its primary mention, so the placement story isn't split away from the type's own row. - Multi-forge addressing paragraph: note the notification path (row 13) now also carries ForgeRef, keeping ForgeRef the one forge-identity vocabulary on both result and notification surfaces. Refs SEA-1731 Co-authored-by: Matt Wilkinson --- .../design.md | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/docs/designs/product/compass-server-ownership-layer-amendment/design.md b/docs/designs/product/compass-server-ownership-layer-amendment/design.md index 6432854c..097be500 100644 --- a/docs/designs/product/compass-server-ownership-layer-amendment/design.md +++ b/docs/designs/product/compass-server-ownership-layer-amendment/design.md @@ -109,7 +109,7 @@ amendment: | 9 | `ForgeChecksSummary`/`ForgeCheck` (#995 :421-431) | **REMOVED** | Replaced by `compass.v1.ChecksSummary`/`Check` (#1018 :222-232), field-for-field identical. | | 10 | The seven op requests (#995 :433-473): `CreateIssueRequest`, `CommentOnIssueRequest`, `GetIssueRequest`, `ListIssuesRequest`, `CreatePullRequestRequest`, `CommentOnPullRequestRequest`, `GetPullRequestRequest` | **SURVIVE** | Confirmed per-request this run: every field in all seven is a scalar (`string`/`uint64`/`uint32`/`bool`/`repeated string`) — no forge domain type appears in any request shape (#995 :433-473). Read-op requests (`GetIssueRequest`, `ListIssuesRequest`, `GetPullRequestRequest`) survive (OQ-A ruled option 3 — answered from the projection/store, §Resolved decisions). One width note: #995 keeps `uint64` issue/pull numbers in these requests (:444-446, :470-472) while the canonical result types carry `uint32 number` (#1018 :178); narrow the surviving request fields to `uint32` to match the canonical width — free now, buf-breaking after the surface ships. | | 11 | `ListIssuesResponse` (#995 :454-456) | **RECONCILED** | `repeated Issue issues = 1` retypes to `repeated compass.v1.Issue`; the read op is answered from the projection/store (OQ-A ruled option 3). | -| 12 | `SubscribeForgeRequest`/`Response`, `UnsubscribeForgeRequest`/`Response`, `ForgeArtifactKind` (#995 :1027-1043) | **SURVIVE** | All scalar/enum fields (`repo`, `kind`, `number`, `subscription_id`); no domain type. | +| 12 | `SubscribeForgeRequest`/`Response`, `UnsubscribeForgeRequest`/`Response`, `ForgeArtifactKind` (#995 :1027-1043) | **SURVIVE** | All scalar/enum fields (`repo`, `kind`, `number`, `subscription_id`); no domain type. (`ForgeArtifactKind` relocates to the new `forge.proto` leaf per DL-161 — see row 13; the subscribe/unsubscribe requests stay in `agent_gateway.proto`.) | | 13 | `ForgeNotification`/`ForgeNotificationKind` (#995 :1191-1212) | **RECONCILED + RELOCATED (DL-161)** | The message and kind enum survive but move to the new leaf `forge.proto` (with `ForgeArtifactKind` + `CommentRef`) to break the notification-placement cycle. Three arms reconcile: `ForgeChecksSummary checks = 9` → `compass.v1.ChecksSummary`; `IssueComment comment = 8` → the ruled `CommentRef` (§Resolved decisions OQ-B); and `string provider = 2` (#995 :1193) is retyped to `compass.v1.ForgeRef forge = 2` — the shipped decision (forge.proto `ForgeRef forge = 2`), so the notification path carries the one `compass.v1` forge-identity vocabulary rather than a second bare-string one. | | 14 | `runner.proto`: `RelayForgeCall` + `RelayForgeCallRequest`/`Response` (#995 :485-493) | **SURVIVES** | Pure wrapper: `ForgeCallRequest call` / `ForgeCallResult result` by reference; reconciliation is inherited from items 2-3. | | 15 | `SessionsResponse.forge_notification = 7` (#995 :1633) | **SURVIVES** | Carries `ForgeNotification`, reconciled internally (item 13). | @@ -123,7 +123,8 @@ is deleted** — six messages removed, four carrier arms retyped to `compass.v1` **Multi-forge request addressing (a reconciliation consequence).** The surviving op requests and `SubscribeForgeRequest` address artifacts by `(repo, number)` with no provider/host (#995 :433-473, :1027-1043), while the -canonical result types now carry `ForgeRef` (#1018 :146-160, DL-091: +canonical result types — and now the notification path (row 13) — carry +`ForgeRef` (#1018 :146-160, DL-091: repo-only coordinates collide under multiple connected forges). #995 even names subscriptions "(provider, repo, kind, number)" (:944-945) though its wire request carries no provider. Ruled (Matt, 2026-07-31, §Resolved @@ -224,22 +225,25 @@ amended proto work, confirmed against the live tree this run. #995's cite: the gen-fence grep script sits at `proto/moon.yml:151` (task `gen-fence` opens at :121), not :141 as #995 recorded — confirmed by reading the live file this run. -- **The gen-fence extension fences the carrier family only.** Extend the grep - with the unanchored `ForgeCall|RelayForgeCall|ForgeNotification| - ForgeArtifactKind` family exactly as #995 T1 specifies (design.md:1636-1639) - — but the canonical `Issue`/`PullRequest`/`ChecksSummary`/`Check`/ - `AgentAttribution` symbols are PUBLIC `compass.proto` types that must - generate into the public trees and MUST NOT be added to the fence. Those - four patterns are safe not because of word-bounding (word-bounding guards - substring collisions like `SessionFrame` vs `AgentSessionFrame`, - `proto/moon.yml:138-150`) but because none of them prefixes a public symbol - — `ForgeRef`/`ForgeProvider` share only the `Forge` stem, no full-pattern - match. The surviving internal-only requests are NOT matched by any pattern - (`CreateIssueRequest`, `CommentOnIssueRequest`, the `Get*`/`List*` requests, - `ListIssuesResponse`, `SubscribeForge*`/`UnsubscribeForge*`, and - `CommentRef`); this gap existed in #995 too — extend the fence with these - word-boundable names (none collides with a public symbol) or accept the gap - explicitly. +- **The gen-fence extension fences the carrier family plus the internal-only + requests.** Extend the grep with the unanchored `ForgeCall|RelayForgeCall| + ForgeNotification|ForgeArtifactKind` family exactly as #995 T1 specifies + (design.md:1636-1639) — but the canonical `Issue`/`PullRequest`/ + `ChecksSummary`/`Check`/`AgentAttribution` symbols are PUBLIC `compass.proto` + types that must generate into the public trees and MUST NOT be added to the + fence. Those four patterns are safe not because of word-bounding + (word-bounding guards substring collisions like `SessionFrame` vs + `AgentSessionFrame`, `proto/moon.yml:138-150`) but because none of them + prefixes a public symbol — `ForgeRef`/`ForgeProvider` share only the `Forge` + stem, no full-pattern match. The surviving internal-only requests are NOT + matched by any of those four family patterns (`CreateIssueRequest`, + `CommentOnIssueRequest`, the `Get*`/`List*` requests, `ListIssuesResponse`, + `SubscribeForge*`/`UnsubscribeForge*`, and `CommentRef`); this gap existed in + #995 too. Decided (A1): the fence IS extended with these word-boundable names + (none collides with a public symbol), not left as an accepted gap — + `CommentRef` in particular MUST be fenced, because DL-161 relocates it into + the internal-only `forge.proto` leaf, so it is a now-internal symbol that + would otherwise leak onto the public gen surface. - **Cross-file import — already satisfiable, handled per-lane.** Retyping the result arms makes `agent_gateway.proto` import `compass/v1/compass.proto`. This is not a new dependency: `agent_gateway.proto` already transitively