diff --git a/docs/designs/DECISIONS.md b/docs/designs/DECISIONS.md index d4102cc06..c1d7e8a8d 100644 --- a/docs/designs/DECISIONS.md +++ b/docs/designs/DECISIONS.md @@ -80,9 +80,9 @@ check enforces the mechanical half. Full rationale: | DL-020 | Postgres is the substrate and is not swappable; the in-memory event bus is a cache/fan-out ring, not a second store | Active (Matt, 2026-07-06) | [architecture lineage](meta/compass-architecture-lineage/design.md) | | DL-021 | The comms substrate is Postgres write-through fan-out, not a swappable NATS-backed seam | Superseded by DL-313 (Matt, 2026-08-31) | [architecture lineage](meta/compass-architecture-lineage/design.md) | | DL-022 | Config distribution is a Runner-mediated pull into a read-only mount; agent binary + base image ride versioned OCI pulls | Active (Matt, 2026-07-04) | [architecture lineage](meta/compass-architecture-lineage/design.md) | -| DL-052 | Only the Server holds forge write credentials, as a `server_only` declared secret filtered out of container injection — a NEW scoping mechanism this record introduces, since none exists in the tree. The agent keeps a push-scoped git credential. The boundary that creates is half enforced (the filter, which Compass controls and tests) and half operator hygiene (the agent token's scope, which Compass neither mints nor inspects); closing the second half would require Compass to hold secret values, which today it deliberately never does | Active (Matt, 2026-07-27) | [ownership layer §Decision 4](product/compass-server-ownership-layer/design.md#decision-4--the-servers-forge-credential-is-a-declared-secret-resolved-not-stored) | -| DL-053 | Forge subscriptions are Server-side Postgres rows with a per-artifact FETCH cursor (advanced on any 200) split from a per-subscriber DELIVERY cursor (advanced only on that subscriber's own successful notify), change-detected by conditional polling in v1 (webhooks are an additive accelerator), delivered by account on the existing `Sessions` → `AgentGateway.Control` push path | Active (Matt, 2026-07-27) | [ownership layer §Decision 5](product/compass-server-ownership-layer/design.md#decision-5--subscriptions-server-stored-poll-based-in-v1-delivered-on-the-existing-push-path) | -| DL-055 | Compass stores a row per artifact it authored (coordinate + agent + owner + session, written with the stamp), so agent ownership queries and the Bridge boards are local index scans over Server-recorded truth rather than forge-wide searches of untrusted header text; it is an ownership index, never a mirror of forge content | Active (Matt, 2026-07-27) | [ownership layer §OQ-8](product/compass-server-ownership-layer/design.md#oq-8-ruled-2026-07-27-matt--recommendation-reversed--does-a-compass-authored-artifact-need-a-compass-side-record) | +| DL-052 | Only the Server holds forge write credentials, as a `server_only` declared secret filtered out of container injection — a NEW scoping mechanism this record introduces, since none exists in the tree. The agent keeps a push-scoped git credential. The boundary that creates is half enforced (the filter, which Compass controls and tests) and half operator hygiene (the agent token's scope, which Compass neither mints nor inspects); closing the second half would require Compass to hold secret values, which today it deliberately never does | Active (Matt, 2026-07-27) | [ownership layer §Decision 4](server/compass-server-ownership-layer/design.md#decision-4--the-servers-forge-credential-is-a-declared-secret-resolved-not-stored) | +| DL-053 | Forge subscriptions are Server-side Postgres rows with a per-artifact FETCH cursor (advanced on any 200) split from a per-subscriber DELIVERY cursor (advanced only on that subscriber's own successful notify), change-detected by conditional polling in v1 (webhooks are an additive accelerator), delivered by account on the existing `Sessions` → `AgentGateway.Control` push path | Active (Matt, 2026-07-27) | [ownership layer §Decision 5](server/compass-server-ownership-layer/design.md#decision-5--subscriptions-server-stored-poll-based-in-v1-delivered-on-the-existing-push-path) | +| DL-055 | Compass stores a row per artifact it authored (coordinate + agent + owner + session, written with the stamp), so agent ownership queries and the Bridge boards are local index scans over Server-recorded truth rather than forge-wide searches of untrusted header text; it is an ownership index, never a mirror of forge content | Active (Matt, 2026-07-27) | [ownership layer §OQ-8](server/compass-server-ownership-layer/design.md#oq-8-ruled-2026-07-27-matt--recommendation-reversed--does-a-compass-authored-artifact-need-a-compass-side-record) | | DL-063 | Agent session transcripts persist to S3 for v1 via an ENDPOINT-AGNOSTIC backend (`COMPASS_S3_ENDPOINT`, generic `Bun.S3Client`, no S3-server-specific calls), reached under default-deny egress via an allowlisted endpoint — the S3 client path is exercised from v1, never a swap-later local-fs abstraction. Solo/self-host backend is a local-filesystem Garage (≥ v2.3.0) per Runner host, granting a full-bucket credential (cross-agent read/tamper accepted only within the single-trust-domain MVP); real/multi-user deployments MUST NOT use the Garage full-bucket credential — they use Cloudflare R2 with prefix-scoped tokens, which is a CONFIG change not a new build (endpoint-agnostic). Docs advisory + hardening follow-up ("R2 + prefix-scoped tokens for real deployments") stated; no bucket-per-agent, no Runner signing proxy | Superseded by DL-084 (Matt, 2026-07-31) | [session persistence §Appendix](agent/compass-agent-session-persistence/design.md#appendix--superseded-by-the-reversal-2026-07-31-the-agent-direct-s3-model) | | DL-064 | The agent persists its transcript DIRECTLY from inside the container as the session runs: a Compass-owned S3 `SessionStorageBackend` wrapped in the SDK's `IndexedSessionStorage`, injected at the cli.ts composition root via `createAgentSession({ sessionManager })`; no runner-ships-the-log-after-teardown step exists. The persist model is a SEGMENTED PER-EPOCH LOG — a stable `session_id` keys the prefix `sessions//` and each container lifetime appends only to its own `.jsonl` segment (continuous per-append full-object PUT of that one segment + terminal drain); reconstruction is an S3 LIST of the prefix reconstructed in ascending epoch order with title-slot CHECKPOINT SUPERSESSION — a segment whose first line parses as a title slot is a full-body-rewrite snapshot that supersedes prior epochs (readFull resets its accumulator to it), so SDK-internal compaction rewrites do not double-count (readFull=LIST+reconstruct vs append=single-current-segment asymmetry). Fresh opens `<0>.jsonl` via `SessionManager.open`; resume opens the PRIOR latest existing epoch key (context is seeded by the backend readFull=LIST+reconstruct on that existing, indexed key) then repoints the write target to a fresh `.jsonl` via the public `captureState`/`restoreState` seams (`session-manager.ts:919/939`, `onDisk:true, needsRewrite:false`), so appends land only in the new segment and prior segments stay immutable — NO SDK patch | Superseded by DL-085 (Matt, 2026-07-31) | [session persistence §Appendix](agent/compass-agent-session-persistence/design.md#appendix--superseded-by-the-reversal-2026-07-31-the-agent-direct-s3-model) | | DL-065 | A Postgres pointer row (`agent_session_transcripts`: stable logical session_id PK → bucket + key PREFIX + a nullable `endpoint` provenance column, FK-rooted in the post-0004 `agent_sessions`) indexes each stored log; resume reuses `StartAgentSession` with an additive authz-gated `resume_session_id` on the PUBLIC request, and the resolved pointer rides a NEW top-level `ResumeContext resume = 12` on the INTERNAL `SessionsResponse` (outside the command oneof, tag 2 untouched — additive, non-forgeable) so a client-set pointer can never reach the Runner (the public request is relayed verbatim). The new container reconstructs one logical log from the prefix and the built replay barrier gates live input (`ReplayComplete` alone lifts it — no TranscriptReplay frames on the resume path) | Superseded by DL-084 (Matt, 2026-07-31) | [session persistence §Appendix](agent/compass-agent-session-persistence/design.md#appendix--superseded-by-the-reversal-2026-07-31-the-agent-direct-s3-model) | @@ -96,18 +96,18 @@ check enforces the mechanical half. Full rationale: | DL-088 | The durable session log and the live client trace are two projections of ONE canonical artifact — the SDK session-entry log: the agent emits committed entries, the Server persists them AND projects the block-level trace from the same store. MVP runs two agent→server streams (durable settled-entry log + ephemeral, non-persisted live per-token trace — token streaming is mandatory); collapsing them into one token stream carrying log metadata is the post-MVP follow-up RIG-1580 | Active (Matt, 2026-07-31) | [session persistence §One canonical artifact](agent/compass-agent-session-persistence/design.md#one-canonical-artifact-the-session-log-unified-with-the-trace-r1) | | DL-089 | The agent persists nothing DURABLE: the injected backend is a TEE — it reads and writes the container-local EPHEMERAL session file normally (so the SDK's own loader, compaction, and rewrites work) and tees each committed write upstream as a durable `TranscriptEntry` frame (append→delta, writeFull→checkpoint, awaited per-path for per-session send order; agent-stamped per-lifetime entry_seq, server-rebased to session scope). Zero S3 credentials, zero storage detail; the local file dies with the container. A definitively-erred emit buffers under a bounded cap with escalating warn→error retry and FAILS THE SESSION at cap exhaustion (resumable from the last committed prefix; cap value is tuning, not freeze-scope) | Active (Matt, 2026-07-31) | [session persistence §The SDK seam](agent/compass-agent-session-persistence/design.md#the-sdk-seam-grounded-revised-for-the-tee) | | DL-093 | The Server's durable session-transcript store is TWO-TIER: a Postgres hot-tail (`agent_session_transcript_entries`) holding `[latest checkpoint..now]` = the resume set, per-frame durable at the `CommitConversationFrame` seam and pruned at flush; superseded entries flush to an S3-compatible object-store cold archive (verbatim JSONL segments, PUT-before-prune, indexed by an `agent_session_archive_segments` manifest) at compaction (primary), a high size-cap safety valve, and session-end. Resume reads PG-only (S3 fallback only when the safety valve fired); the archive feeds the post-MVP opt-in analytics layer. Agent+Runner hold zero storage detail; the object-store client/credential is Server-internal (endpoint-agnostic: Garage/R2/MinIO). Realigns with DL-019 | Active (Matt, 2026-07-31) | [session persistence §T4](agent/compass-agent-session-persistence/design.md#t4-compass-server--the-two-tier-durable-transcript-store-pg-hot-tail--object-store-cold-archive) | -| DL-161 | The board-ingestion forge poll is a ctx-cancellable driver in `internal/ingest` (immediate first pass then `time.Ticker`, sequential per-repo, log-and-continue on per-repo error, `nil` on ctx cancel) that instantiates the DL-053 FETCH-cursor model at repo-LIST granularity: a new `forge_list_cursors` table (per `(provider, host, repo, page)` ETag + hasNext) whose page cursor advances ONLY after every issue on that page durably sinks, so the board holds no per-subscriber DELIVERY cursor (its notify is the synchronous in-pass projection sink). One hand-rolled `net/http` GitHub read client (OQ-6 frozen: no new dep), a fail-fast `x-ratelimit-*` budget gate (never in-client sleep), and a DL-052 `server_only` token resolved behind a short TTL. Builds the FETCH half of DL-053; PR-C (DL-129 tracker-status) and the agent-notification slice both ride it | Superseded by DL-281 (Matt, 2026-08-27) | [forge-poll driver §Approach](product/compass-forge-poll-driver/design.md#approach) | -| DL-162 | The board's poll targets are durable TABLE rows, not config: a new `forge_repo_subscriptions` table keyed `(forge_provider, forge_host, repo)` with an `enabled` soft-disable flag, enumerated `WHERE enabled` at the top of every pass so add/remove is a live row op needing no restart or deploy. The `--forge-repos` CLI flag becomes a declarative boot SEED reconciled into the table `ON CONFLICT DO NOTHING` (bootstrap-only insert: the table is authoritative after first insert, a soft-disabled row stays disabled across restarts, the seed never deletes/disables/re-enables an existing row); a `--forge-poll` bool runs the driver with an empty seed. The dynamic add/remove mutation RPC/admin surface is a named non-goal of this slice | Active (Matt, 2026-08-08) | [forge-poll driver §OQ-C](product/compass-forge-poll-driver/design.md#oq-c--resolved-matt-2026-08-08-the-boards-subscription-set-is-a-table) | -| DL-163 | Migration `0015_forge_subscriptions.sql` lands FOUR tables unconditionally — `forge_repo_subscriptions` (DL-162), `forge_list_cursors` (DL-161), and the two DL-053-spec'd tables `agent_forge_subscriptions` (DL-053's `forge_subscriptions`, RENAMED to disambiguate the per-artifact agent-owned subscription from the board's per-repo target) and `forge_artifact_cursors` (both writer-less this slice, shape frozen + pgtest-covered before their writers) — every key coordinate-aligned to the 0013 issue convention (SMALLINT provider + `forge_host`) and every provider CHECK admitting the full declared proto enum `IN (1, 2, 3, 4)` (the CHECK's job is "never UNSPECIFIED(0)", not gating rollout; rollout is gated by which `forge.Provider` has a real client — GitHub only this slice). The `issues` CHECK `IN (1, 2, 3)` widening for Linear ingestion is a separate future-slice prerequisite (OQ-E) | Active (Matt, 2026-08-08) | [forge-poll driver §OQ-D](product/compass-forge-poll-driver/design.md#oq-d--resolved-matt-2026-08-08-all-four-tables-land-now-checks-admit-the-full-enum) | -| DL-264 | The DL-053 agent-notification transport is WEBHOOK-ONLY: a per-deployment GitHub App (one App-level webhook URL + secret, install-to-subscribe, `X-Hub-Signature-256` fail-closed) posting to `POST /webhooks/github` on the network TLS door, and Linear `Issue`/`Comment` data-change events on the RIG-2717 `POST /webhooks` handler (implementation in flight — Global Constraints) — no standing poll loop exists on this lane. Reliability is a bounded reconciliation catch-up: one conditional-GET sweep at startup plus a 30 min backstop that diffs snapshots via `DetectChanges` and re-notifies from the durable `delivered_revision` gap (synthesizing one payload-free UPDATE when the missed set is no longer derivable) — a gap-healer at tens-of-minutes cadence, never a primary transport. Supersedes DL-053's transport premise; its two-cursor split and delivery semantics survive. | Active (Matt, 2026-08-26) | [forge agent-notification §transport pivot](product/compass-forge-agent-notification/design.md#the-transport-pivot-dl-053s-poll-premise-and-why-the-app-dissolves-it) | -| DL-265 | Forge notifications ride the generic `DispatchControl` relay (`SessionsResponse.deliver_control` envelope wrapping `AgentControl.forge_notification`), NOT the bare `SessionsResponse.forge_notification = 7` variant — that variant predates the generic relay (`compass-notification-delivery/design.md:199-202`) and is superseded: it stays reserved on the wire with a doc comment, and no Runner dispatch arm is built for it. Zero new Runner code on the relay path. Agent-side delivery idempotency is control-rail SEQ-based (`acks.isApplied`, `packages/compass-agent/src/transport/control-source.ts:363-367`); no content-tuple key exists on this lane, and duplicate content is within the at-least-once contract. | Active (Matt, 2026-08-26) | [forge agent-notification §pipeline](product/compass-forge-agent-notification/design.md#the-pipeline-end-to-end) | -| DL-266 | The forge delivery cursor (`delivered_revision`) advances on the agent's in-band forge delivery ack — `ForgeNotificationAck{subscription_id, revision}`, an additive AgentFrame variant emitted at turn-end flush — NEVER on dispatch success (Matt's W3 ruling, option beta), aligning this lane with the dispatch arm's own model ("The cursor is never advanced on send — it advances only later on the recipient's delivery_ack", `go/internal/runnerhub/dispatch_control.go:31-33`). Every pre-ack loss (synchronous refusal, async RunnerError via `router.complete`, agent death before flush) leaves the cursor unadvanced and is healed by the reconcile sweep within one backstop interval. The correlation is new work: today's `delivery_ack` carries only a comms `message_id` (`agent.proto:232-239`). This reopens the frozen delivery record's advance-signal clause (`compass-notification-delivery/design.md:894-897`) and rides the freeze-gate ratification, bundled with the fresh-subscription catch-up baseline moving from DL-053's "at Subscribe time" to first-observed-event/sweep (bounded by ≤1 backstop interval) — also decided in W3's ruling. | Active (Matt, 2026-08-26) | [forge agent-notification §W3](product/compass-forge-agent-notification/design.md#resolved-decisions-matt-2026-08-26) | -| DL-267 | Forge subscriptions gain CONTAINER-SCOPE granularity via an explicit `ForgeSubscriptionScope` enum on `SubscribeForgeRequest` — Matt's W2 ruling, option (b); the `number = 0` sentinel is REJECTED (proto3 absent-vs-0 blindness would convert a forgotten `number` from an LLM tool caller into a silent whole-repo subscription). Containers are PROVIDER-NATIVE: GitHub's container is the REPO (the existing `repo` slot); Linear's is a PROJECT, carried in a slot of its own and never overloaded onto the team key in `repo` — Linear project scope is thereby IN scope (promoted from deferral). The project identifier lives in a dedicated column (OQ-1, ruled (i): additive `scope` + `project` columns, UNIQUE widened). A container subscription delivers exactly `FORGE_NOTIFICATION_KIND_OPENED` with the new artifact's number/url — never a fan-in of every event on every artifact. Two additive `ForgeNotificationKind` values land with it: `REVIEW = 5` (submitted PR review; free on webhooks where the poll design priced no review endpoint) and `OPENED = 6`. | Active (Matt, 2026-08-26) | [forge agent-notification §subscription model](product/compass-forge-agent-notification/design.md#the-subscription-model-folded-per-matts-rulings-3--w2) | -| DL-281 | Board issue ingestion is WEBHOOK-DRIVEN, not polled: the GitHub App webhook ingress (DL-264's `POST /webhooks/github`) fans accepted `issues` events to a board ingest arm that normalizes the repo, gates on `forge_repo_subscriptions` (DL-162, kept), hydrates each coordinate via a conditional GET, and sinks through the one `StripOwner`→`TranslateIssue`→stamp pipeline into `IssueProjection.PublishIssueUpdate`; reliability + cold-start are a bounded per-repo updated-order reconcile sweep (startup + 30-min ticker, per-repo `updated_at` watermark advanced only after sink), the DL-264 backstop pattern at repo-LIST granularity. Retires the DL-161 standing poll driver + `forge_list_cursors`; amends DL-053's "conditional polling in v1" transport clause (webhooks primary, conditional reads only inside the sweep) and DL-163's four-table set (`forge_list_cursors` dropped by editing `0001_init.sql` in place — Compass is pre-live, the init migration is edited directly; additive watermark + `forge_updated_at` recency-guard columns added the same way). The GitHub App is the ONLY GitHub read credential — the static read-path PAT is retired, completing the App-only cutover. Known pre-existing limit (poll had it identically): no transport removes a board row — a forge-deleted/transferred issue persists until manually removed | Active (Matt, 2026-08-27) | [board webhook ingestion §Approach](product/compass-forge-board-webhook-ingestion/design.md#approach) | -| DL-304 | The notify-path snapshot comment set is keyed by a provider-stable string comment id (`CommentRef.comment_key = 6`; GitHub numeric id as string, Linear UUID) — NOT the comment URL, which the Linear COMMENT webhook payload cannot carry (it has only the parent issue URL, so a URL-keyed snapshot diverged between the webhook producer (issue URL) and the sweep producer (comment URL), a phantom-diff heartbeat). Both `SnapshotComment` and the digest drop the per-comment URL. | Active (Matt, 2026-08-30) | [agent-notification record §Global Constraints](product/compass-forge-agent-notification/design.md#global-constraints) | -| DL-269 | Every request-input account field on the compass proto contract is handle-typed; the Server resolves handle→account_id at the service edge (the `from_handle` posture generalized). An unresolvable, invisible, foreign, or wrong-subtype handle is ONE indistinguishable in-band NOT_FOUND naming the submitted handle — the oracle invariant that forbids a caller distinguishing "exists under another owner" from "no such handle" (the ReparentAgent foreign-parent leg mirrors CreateAgent's same-owner edge pre-check to hold this). No agent or client UI ever resolves an id. | Active (Matt, 2026-08-27) | [handle-addressing cutover §Ledger impact](product/compass-handle-addressing-cutover/design.md#ledger-impact) | -| DL-270 | Response, stored, and event account fields stay id-typed — ids are the stable join keys clients already hold. A response that needs a handle for display carries it as an explicit sibling field (the `RosterEntry.agent_account_id`+`handle` dual), never by retyping the id field; the response-side getters (`SpawnPeerResponse`/`RosterEntry`/`AgentSessionStatus`) keep their id accessors. | Active (Matt, 2026-08-27) | [handle-addressing cutover §Ledger impact](product/compass-handle-addressing-cutover/design.md#ledger-impact) | -| DL-271 | Handle→id resolution is owner-namespaced, stored in a dedicated `account_handles(account_id, handle, owner_user_id NULL)` table with two partial-unique indexes (user/system handles globally unique; agent handles unique per owner). Agents are addressed owner-qualified (`matt/compass-ux`, bare = caller's own owner); users/system bare. Rename is in-place for both tiers; reclaim is allowed for both tiers (no history, tombstone, or reservation) — cross-human reclaim safety is the owner-peering authorization edge (RIG-2796), not handle reservation. The `handle` column stays on `accounts` for display only; `account_handles` is the resolution key. | Active (Matt, 2026-08-27) | [handle-addressing cutover §Ledger impact](product/compass-handle-addressing-cutover/design.md#ledger-impact) | +| DL-161 | The board-ingestion forge poll is a ctx-cancellable driver in `internal/ingest` (immediate first pass then `time.Ticker`, sequential per-repo, log-and-continue on per-repo error, `nil` on ctx cancel) that instantiates the DL-053 FETCH-cursor model at repo-LIST granularity: a new `forge_list_cursors` table (per `(provider, host, repo, page)` ETag + hasNext) whose page cursor advances ONLY after every issue on that page durably sinks, so the board holds no per-subscriber DELIVERY cursor (its notify is the synchronous in-pass projection sink). One hand-rolled `net/http` GitHub read client (OQ-6 frozen: no new dep), a fail-fast `x-ratelimit-*` budget gate (never in-client sleep), and a DL-052 `server_only` token resolved behind a short TTL. Builds the FETCH half of DL-053; PR-C (DL-129 tracker-status) and the agent-notification slice both ride it | Superseded by DL-281 (Matt, 2026-08-27) | [forge-poll driver §Approach](server/compass-forge-poll-driver/design.md#approach) | +| DL-162 | The board's poll targets are durable TABLE rows, not config: a new `forge_repo_subscriptions` table keyed `(forge_provider, forge_host, repo)` with an `enabled` soft-disable flag, enumerated `WHERE enabled` at the top of every pass so add/remove is a live row op needing no restart or deploy. The `--forge-repos` CLI flag becomes a declarative boot SEED reconciled into the table `ON CONFLICT DO NOTHING` (bootstrap-only insert: the table is authoritative after first insert, a soft-disabled row stays disabled across restarts, the seed never deletes/disables/re-enables an existing row); a `--forge-poll` bool runs the driver with an empty seed. The dynamic add/remove mutation RPC/admin surface is a named non-goal of this slice | Active (Matt, 2026-08-08) | [forge-poll driver §OQ-C](server/compass-forge-poll-driver/design.md#oq-c--resolved-matt-2026-08-08-the-boards-subscription-set-is-a-table) | +| DL-163 | Migration `0015_forge_subscriptions.sql` lands FOUR tables unconditionally — `forge_repo_subscriptions` (DL-162), `forge_list_cursors` (DL-161), and the two DL-053-spec'd tables `agent_forge_subscriptions` (DL-053's `forge_subscriptions`, RENAMED to disambiguate the per-artifact agent-owned subscription from the board's per-repo target) and `forge_artifact_cursors` (both writer-less this slice, shape frozen + pgtest-covered before their writers) — every key coordinate-aligned to the 0013 issue convention (SMALLINT provider + `forge_host`) and every provider CHECK admitting the full declared proto enum `IN (1, 2, 3, 4)` (the CHECK's job is "never UNSPECIFIED(0)", not gating rollout; rollout is gated by which `forge.Provider` has a real client — GitHub only this slice). The `issues` CHECK `IN (1, 2, 3)` widening for Linear ingestion is a separate future-slice prerequisite (OQ-E) | Active (Matt, 2026-08-08) | [forge-poll driver §OQ-D](server/compass-forge-poll-driver/design.md#oq-d--resolved-matt-2026-08-08-all-four-tables-land-now-checks-admit-the-full-enum) | +| DL-264 | The DL-053 agent-notification transport is WEBHOOK-ONLY: a per-deployment GitHub App (one App-level webhook URL + secret, install-to-subscribe, `X-Hub-Signature-256` fail-closed) posting to `POST /webhooks/github` on the network TLS door, and Linear `Issue`/`Comment` data-change events on the RIG-2717 `POST /webhooks` handler (implementation in flight — Global Constraints) — no standing poll loop exists on this lane. Reliability is a bounded reconciliation catch-up: one conditional-GET sweep at startup plus a 30 min backstop that diffs snapshots via `DetectChanges` and re-notifies from the durable `delivered_revision` gap (synthesizing one payload-free UPDATE when the missed set is no longer derivable) — a gap-healer at tens-of-minutes cadence, never a primary transport. Supersedes DL-053's transport premise; its two-cursor split and delivery semantics survive. | Active (Matt, 2026-08-26) | [forge agent-notification §transport pivot](server/compass-forge-agent-notification/design.md#the-transport-pivot-dl-053s-poll-premise-and-why-the-app-dissolves-it) | +| DL-265 | Forge notifications ride the generic `DispatchControl` relay (`SessionsResponse.deliver_control` envelope wrapping `AgentControl.forge_notification`), NOT the bare `SessionsResponse.forge_notification = 7` variant — that variant predates the generic relay (`compass-notification-delivery/design.md:199-202`) and is superseded: it stays reserved on the wire with a doc comment, and no Runner dispatch arm is built for it. Zero new Runner code on the relay path. Agent-side delivery idempotency is control-rail SEQ-based (`acks.isApplied`, `packages/compass-agent/src/transport/control-source.ts:363-367`); no content-tuple key exists on this lane, and duplicate content is within the at-least-once contract. | Active (Matt, 2026-08-26) | [forge agent-notification §pipeline](server/compass-forge-agent-notification/design.md#the-pipeline-end-to-end) | +| DL-266 | The forge delivery cursor (`delivered_revision`) advances on the agent's in-band forge delivery ack — `ForgeNotificationAck{subscription_id, revision}`, an additive AgentFrame variant emitted at turn-end flush — NEVER on dispatch success (Matt's W3 ruling, option beta), aligning this lane with the dispatch arm's own model ("The cursor is never advanced on send — it advances only later on the recipient's delivery_ack", `go/internal/runnerhub/dispatch_control.go:31-33`). Every pre-ack loss (synchronous refusal, async RunnerError via `router.complete`, agent death before flush) leaves the cursor unadvanced and is healed by the reconcile sweep within one backstop interval. The correlation is new work: today's `delivery_ack` carries only a comms `message_id` (`agent.proto:232-239`). This reopens the frozen delivery record's advance-signal clause (`compass-notification-delivery/design.md:894-897`) and rides the freeze-gate ratification, bundled with the fresh-subscription catch-up baseline moving from DL-053's "at Subscribe time" to first-observed-event/sweep (bounded by ≤1 backstop interval) — also decided in W3's ruling. | Active (Matt, 2026-08-26) | [forge agent-notification §W3](server/compass-forge-agent-notification/design.md#resolved-decisions-matt-2026-08-26) | +| DL-267 | Forge subscriptions gain CONTAINER-SCOPE granularity via an explicit `ForgeSubscriptionScope` enum on `SubscribeForgeRequest` — Matt's W2 ruling, option (b); the `number = 0` sentinel is REJECTED (proto3 absent-vs-0 blindness would convert a forgotten `number` from an LLM tool caller into a silent whole-repo subscription). Containers are PROVIDER-NATIVE: GitHub's container is the REPO (the existing `repo` slot); Linear's is a PROJECT, carried in a slot of its own and never overloaded onto the team key in `repo` — Linear project scope is thereby IN scope (promoted from deferral). The project identifier lives in a dedicated column (OQ-1, ruled (i): additive `scope` + `project` columns, UNIQUE widened). A container subscription delivers exactly `FORGE_NOTIFICATION_KIND_OPENED` with the new artifact's number/url — never a fan-in of every event on every artifact. Two additive `ForgeNotificationKind` values land with it: `REVIEW = 5` (submitted PR review; free on webhooks where the poll design priced no review endpoint) and `OPENED = 6`. | Active (Matt, 2026-08-26) | [forge agent-notification §subscription model](server/compass-forge-agent-notification/design.md#the-subscription-model-folded-per-matts-rulings-3--w2) | +| DL-281 | Board issue ingestion is WEBHOOK-DRIVEN, not polled: the GitHub App webhook ingress (DL-264's `POST /webhooks/github`) fans accepted `issues` events to a board ingest arm that normalizes the repo, gates on `forge_repo_subscriptions` (DL-162, kept), hydrates each coordinate via a conditional GET, and sinks through the one `StripOwner`→`TranslateIssue`→stamp pipeline into `IssueProjection.PublishIssueUpdate`; reliability + cold-start are a bounded per-repo updated-order reconcile sweep (startup + 30-min ticker, per-repo `updated_at` watermark advanced only after sink), the DL-264 backstop pattern at repo-LIST granularity. Retires the DL-161 standing poll driver + `forge_list_cursors`; amends DL-053's "conditional polling in v1" transport clause (webhooks primary, conditional reads only inside the sweep) and DL-163's four-table set (`forge_list_cursors` dropped by editing `0001_init.sql` in place — Compass is pre-live, the init migration is edited directly; additive watermark + `forge_updated_at` recency-guard columns added the same way). The GitHub App is the ONLY GitHub read credential — the static read-path PAT is retired, completing the App-only cutover. Known pre-existing limit (poll had it identically): no transport removes a board row — a forge-deleted/transferred issue persists until manually removed | Active (Matt, 2026-08-27) | [board webhook ingestion §Approach](server/compass-forge-board-webhook-ingestion/design.md#approach) | +| DL-304 | The notify-path snapshot comment set is keyed by a provider-stable string comment id (`CommentRef.comment_key = 6`; GitHub numeric id as string, Linear UUID) — NOT the comment URL, which the Linear COMMENT webhook payload cannot carry (it has only the parent issue URL, so a URL-keyed snapshot diverged between the webhook producer (issue URL) and the sweep producer (comment URL), a phantom-diff heartbeat). Both `SnapshotComment` and the digest drop the per-comment URL. | Active (Matt, 2026-08-30) | [agent-notification record §Global Constraints](server/compass-forge-agent-notification/design.md#global-constraints) | +| DL-269 | Every request-input account field on the compass proto contract is handle-typed; the Server resolves handle→account_id at the service edge (the `from_handle` posture generalized). An unresolvable, invisible, foreign, or wrong-subtype handle is ONE indistinguishable in-band NOT_FOUND naming the submitted handle — the oracle invariant that forbids a caller distinguishing "exists under another owner" from "no such handle" (the ReparentAgent foreign-parent leg mirrors CreateAgent's same-owner edge pre-check to hold this). No agent or client UI ever resolves an id. | Active (Matt, 2026-08-27) | [handle-addressing cutover §Ledger impact](server/compass-handle-addressing-cutover/design.md#ledger-impact) | +| DL-270 | Response, stored, and event account fields stay id-typed — ids are the stable join keys clients already hold. A response that needs a handle for display carries it as an explicit sibling field (the `RosterEntry.agent_account_id`+`handle` dual), never by retyping the id field; the response-side getters (`SpawnPeerResponse`/`RosterEntry`/`AgentSessionStatus`) keep their id accessors. | Active (Matt, 2026-08-27) | [handle-addressing cutover §Ledger impact](server/compass-handle-addressing-cutover/design.md#ledger-impact) | +| DL-271 | Handle→id resolution is owner-namespaced, stored in a dedicated `account_handles(account_id, handle, owner_user_id NULL)` table with two partial-unique indexes (user/system handles globally unique; agent handles unique per owner). Agents are addressed owner-qualified (`matt/compass-ux`, bare = caller's own owner); users/system bare. Rename is in-place for both tiers; reclaim is allowed for both tiers (no history, tombstone, or reservation) — cross-human reclaim safety is the owner-peering authorization edge (RIG-2796), not handle reservation. The `handle` column stays on `accounts` for display only; `account_handles` is the resolution key. | Active (Matt, 2026-08-27) | [handle-addressing cutover §Ledger impact](server/compass-handle-addressing-cutover/design.md#ledger-impact) | | DL-311 | Multi-tenant isolation is one shared Postgres database with a `tenant_id` column and row-level security enforced per transaction (fail-closed `SET LOCAL` GUC scoping); a compliance-sensitive tenant may be promoted to a dedicated database running the identical schema (escape hatch, not a fork); the OSS core runs the same schema single-tenant with one bootstrap tenant row (RIG-2861 OQ-2 = RLS) | Active (Matt, 2026-08-31) | [multi-tenancy & NATS substrate](infra/runtime/compass-managed-multitenancy/design.md#q1--tenant-data-isolation-shared-db--tenant_id--postgres-rls) | | DL-314 | Postgres remains the store of record and the sole durability source of truth for committed comms/routing/session-binding state (restating DL-019's surviving "Postgres is the store of record" clause verbatim): JetStream is an at-least-once delivery TRANSPORT whose consumer state is disposable, never a second truth store; every fabric recovery path terminates in a Postgres cursor or row. Supersedes DL-019's "JetStream is comms-only" clause only — its store-of-record and transcript-blob-seam clauses (DL-093) survive (RIG-2861 OQ-1) | Active (Matt, 2026-08-31) | [multi-tenancy & NATS substrate](infra/runtime/compass-managed-multitenancy/design.md#q3--the-eventing-substrate-one-nats-eventfabric-a-standalone-stack-service-jetstream-as-the-delivery-transport) | | DL-315 | The cross-tenant background/system loops (delivery-cursor sweep, deliver-ack advance, reattach recovery, lag-resync) run under a narrowly-scoped `BYPASSRLS` system role granted ONLY to those named background workers and NEVER on the request path; every request-path query stays fail-closed under RLS (RIG-2861 OQ-4 = option 1) | Active (Matt, 2026-08-31) | [multi-tenancy & NATS substrate](infra/runtime/compass-managed-multitenancy/design.md#resolved-decisions-freeze--matt-2026-08-31) | @@ -154,34 +154,34 @@ check enforces the mechanical half. Full rationale: | DL-211 | The agent-side ask round-trip rides the comms transport: raise = a dedicated native `comms_post_ask` tool posting `PostMessage(MessageBlock{ask})` onto the agent's home channel into a named topic/thread (like `comms_post_message`), non-blocking; consume = `AskAnswerControl` over the control lane, correlated by server-minted `ask_id`. The session stays observe+stop only (no promptable session; no reply path). The session-id keying of the answer wake is a wart — an owed answer is keyed on the agent HANDLE, delivered to whatever session is next live for it (a runner/hub delivery-lane fix, entangled RIG-1310); when the asking agent is not live the human decides recovery (relaunch it, or route the answer to a new agent) — deliberately no agent-side boot poll | Active (Matt, 2026-08-18) | [ask comms round-trip §Approach](agent/compass-ask-comms-roundtrip/design.md#approach) | | DL-212 | The agent comms native toolset is `comms_post_message`, `comms_list_messages`, `comms_roster`, `comms_set_status`, and (DL-211) `comms_post_ask` — five tools; refreshes DL-028's "two native tools" count (already four in code before `comms_post_ask`). DL-028's load-bearing clause — the agent may RAISE an ask but has NO ask-ANSWERING capability, structurally (the request oneof cannot express RespondToAsk) — carries forward unchanged | Active (Matt, 2026-08-18) | [ask comms round-trip §Decisions](agent/compass-ask-comms-roundtrip/design.md#decisions-ratified-by-matt--this-record-is-post-fork) | | DL-029 | Agent comms identity/authz is session-resolved server-side, defaulting to the home channel | Active (Matt, 2026-07-22) | [agent comms tools §Identity / authz](agent/compass-agent-comms-tools/design.md#identity--authz--session-resolved-server-side-home-channel-default) | -| DL-048 | The Server is the ownership layer: agents reach Issues/Chat/PRs only through the Server, which stamps a machine-parseable author/owner header — replacing per-agent forge accounts (Forgejo/Plane deprioritised) | Active (Matt, 2026-07-27) | [ownership layer §Approach](product/compass-server-ownership-layer/design.md#approach) | -| DL-049 | Forge tools ride the existing `AgentGateway` socket as a sibling `ForgeCall*` family relayed by `RelayForgeCall`, not a `CommsCallRequest` extension and not a second channel | Active (Matt, 2026-07-27) | [ownership layer §Decision 1](product/compass-server-ownership-layer/design.md#decision-1--a-sibling-forgecallrequest-on-the-same-agentgateway-socket) | -| DL-050 | The `compass:owner` header is a top-placed HTML comment + rendered line, stamped at one chokepoint (`forge.StampOwner`), idempotent, unforgeable ON WRITE; a header PARSED from forge text is untrusted display metadata that may not reach any authz/routing/ownership decision | Active (Matt, 2026-07-27) | [ownership layer §Decision 2](product/compass-server-ownership-layer/design.md#decision-2--the-authorowner-header-a-fenced-html-comment-plus-a-visible-line) | -| DL-051 | The forge adapter is `go/internal/forge` behind a swappable `Provider` interface (GitHub first, Linear issues-only), wired as a `runnerhub` sink | Active (Matt, 2026-07-27) | [ownership layer §Decision 3](product/compass-server-ownership-layer/design.md#decision-3--the-forge-adapter-lives-in-a-new-gointernalforge-package) | -| DL-054 | Notifications v1 is chat pings plus asks on the existing comms surfaces; no notifications page, centre, badge, or read state | Active (Matt, 2026-07-27) | [ownership layer §Decision 6](product/compass-server-ownership-layer/design.md#decision-6--notifications-v1-pings-in-chat-plus-asks-nothing-else) | -| DL-071 | Channel→agent delivery is a Server-side comms-bus consumer dispatching `deliver`/`steer` ops through the RunnerHub over a generic `SessionsResponse.DispatchControl` relay variant onto the built control lane; agent-authored (streaming) messages are held and delivered at the author's turn-settle edge (WORKING → READY/IDLE), human-authored at post (OQ-7, Matt 2026-07-29) | Active (Matt, 2026-07-29) | [notification delivery §D1](product/compass-notification-delivery/design.md#d1--the-fan-out-consumer-a-server-side-bus-consumer-dispatching-over-the-existing-sessions-relay) | -| DL-072 | The durable delivery cursor is Server-owned: a contiguous low-water cursor plus a bounded above-cursor set per `(agent_account_id, channel_id)` on `messages.seq` (mirroring `ControlAck`'s `acked_seq` + `applied_above`), reconstructed from per-message acks, swept gap-aware on session start/reconnect; the per-(agent, channel) keying is ratified (Matt, 2026-07-29), amending RT-3's "per-session" wording | Active (Matt, 2026-07-29) | [notification delivery §D2](product/compass-notification-delivery/design.md#d2--the-durable-delivery-cursor-server-owned-keyed-agent_account-channel-advanced-on-ack) | -| DL-073 | `DeliverControl` (and channel-borne `SteerControl`) carry the full first-party `compass.v1.Message` and nothing else (no `channel_seq`); the agent acks per message via `AgentFrame.delivery_ack { message_id }` — the frozen ack shape | Active (Matt, 2026-07-29) | [notification delivery §D3](product/compass-notification-delivery/design.md#d3--delivercontrol-payload-the-first-party-compassv1message-the-ack-is-the-message-id) | -| DL-074 | MVP presence is 4-state, Cotal-aligned (WORKING/IDLE/WAITING/OFFLINE): WORKING/IDLE/OFFLINE derived from the agent-session lifecycle, WAITING a server-side unanswered-ask overlay (an authored `Ask` with `Ask.answered=false` in a visible channel; WAITING > IDLE), no heartbeat, published as an additive `SubscribeCommsResponse` variant visible to actors sharing at least one visible channel with the agent, in-memory only with re-enroll reconciliation | Active (Matt, 2026-07-29) | [notification delivery §D4](product/compass-notification-delivery/design.md#d4--presence-derived-from-the-session-lifecycle-no-heartbeat) | +| DL-048 | The Server is the ownership layer: agents reach Issues/Chat/PRs only through the Server, which stamps a machine-parseable author/owner header — replacing per-agent forge accounts (Forgejo/Plane deprioritised) | Active (Matt, 2026-07-27) | [ownership layer §Approach](server/compass-server-ownership-layer/design.md#approach) | +| DL-049 | Forge tools ride the existing `AgentGateway` socket as a sibling `ForgeCall*` family relayed by `RelayForgeCall`, not a `CommsCallRequest` extension and not a second channel | Active (Matt, 2026-07-27) | [ownership layer §Decision 1](server/compass-server-ownership-layer/design.md#decision-1--a-sibling-forgecallrequest-on-the-same-agentgateway-socket) | +| DL-050 | The `compass:owner` header is a top-placed HTML comment + rendered line, stamped at one chokepoint (`forge.StampOwner`), idempotent, unforgeable ON WRITE; a header PARSED from forge text is untrusted display metadata that may not reach any authz/routing/ownership decision | Active (Matt, 2026-07-27) | [ownership layer §Decision 2](server/compass-server-ownership-layer/design.md#decision-2--the-authorowner-header-a-fenced-html-comment-plus-a-visible-line) | +| DL-051 | The forge adapter is `go/internal/forge` behind a swappable `Provider` interface (GitHub first, Linear issues-only), wired as a `runnerhub` sink | Active (Matt, 2026-07-27) | [ownership layer §Decision 3](server/compass-server-ownership-layer/design.md#decision-3--the-forge-adapter-lives-in-a-new-gointernalforge-package) | +| DL-054 | Notifications v1 is chat pings plus asks on the existing comms surfaces; no notifications page, centre, badge, or read state | Active (Matt, 2026-07-27) | [ownership layer §Decision 6](server/compass-server-ownership-layer/design.md#decision-6--notifications-v1-pings-in-chat-plus-asks-nothing-else) | +| DL-071 | Channel→agent delivery is a Server-side comms-bus consumer dispatching `deliver`/`steer` ops through the RunnerHub over a generic `SessionsResponse.DispatchControl` relay variant onto the built control lane; agent-authored (streaming) messages are held and delivered at the author's turn-settle edge (WORKING → READY/IDLE), human-authored at post (OQ-7, Matt 2026-07-29) | Active (Matt, 2026-07-29) | [notification delivery §D1](server/compass-notification-delivery/design.md#d1--the-fan-out-consumer-a-server-side-bus-consumer-dispatching-over-the-existing-sessions-relay) | +| DL-072 | The durable delivery cursor is Server-owned: a contiguous low-water cursor plus a bounded above-cursor set per `(agent_account_id, channel_id)` on `messages.seq` (mirroring `ControlAck`'s `acked_seq` + `applied_above`), reconstructed from per-message acks, swept gap-aware on session start/reconnect; the per-(agent, channel) keying is ratified (Matt, 2026-07-29), amending RT-3's "per-session" wording | Active (Matt, 2026-07-29) | [notification delivery §D2](server/compass-notification-delivery/design.md#d2--the-durable-delivery-cursor-server-owned-keyed-agent_account-channel-advanced-on-ack) | +| DL-073 | `DeliverControl` (and channel-borne `SteerControl`) carry the full first-party `compass.v1.Message` and nothing else (no `channel_seq`); the agent acks per message via `AgentFrame.delivery_ack { message_id }` — the frozen ack shape | Active (Matt, 2026-07-29) | [notification delivery §D3](server/compass-notification-delivery/design.md#d3--delivercontrol-payload-the-first-party-compassv1message-the-ack-is-the-message-id) | +| DL-074 | MVP presence is 4-state, Cotal-aligned (WORKING/IDLE/WAITING/OFFLINE): WORKING/IDLE/OFFLINE derived from the agent-session lifecycle, WAITING a server-side unanswered-ask overlay (an authored `Ask` with `Ask.answered=false` in a visible channel; WAITING > IDLE), no heartbeat, published as an additive `SubscribeCommsResponse` variant visible to actors sharing at least one visible channel with the agent, in-memory only with re-enroll reconciliation | Active (Matt, 2026-07-29) | [notification delivery §D4](server/compass-notification-delivery/design.md#d4--presence-derived-from-the-session-lifecycle-no-heartbeat) | | DL-075 | Agent-facing spawn/despawn is owned by the spawning agent's owner (the human): all wave agents share `AgentAccount.OwnerUserID`, and despawn authority is same-owner-scoped for the MVP (finer caller-scoped authz deferred to RIG-1573) | Active (Matt, 2026-07-29) | [spawn/despawn §Identity and authz](agent/compass-agent-spawn-despawn/design.md#identity-and-authz--owner-inherited-from-the-caller-resolved-at-the-rpc-edge) | | DL-076 | Spawn/despawn rides a sibling `LifecycleCall*` family on the existing `AgentGateway` socket relayed by `RelayLifecycleCall` (mirroring DL-049), orchestrated server-side via a `LifecycleCaller` seam while the runnerhub resolves caller identity only; there is no public agent-callable spawn RPC (the egress-sealed agent holds no server token) | Active (Matt, 2026-07-29) | [spawn/despawn §Seam (a)](agent/compass-agent-spawn-despawn/design.md#seam-a--spawn-a-lifecyclecall-family-on-the-agent-gateway-orchestrated-server-side) | | DL-077 | Despawn stops the live session and calls a NEW public `RemoveAgentWorkspace` RPC (`compass.proto`, deliberately outside the RIG-1267 gen-fence); the agent account persists (teardown is compute-only). The MVP accepts the Runner's serial-dispatch head-of-line blocking, guarded by a bounded relay deadline as the sole guard with no spawn rate cap (limits → RIG-1574, concurrent dispatch → RIG-1575) | Active (Matt, 2026-07-29) | [spawn/despawn §Seam (b)](agent/compass-agent-spawn-despawn/design.md#seam-b--despawn--the-new-removeagentworkspace-rpc) | | DL-090 | Repo/auto-clone is removed from every agent-start contract: neither SpawnPeerRequest nor ProvisionAgentWorkspaceRequest carries repo/ref; agents self-clone after launch with the container's scoped git credential (post-MVP: a clone tool so agents hold no creds). Resolves OQ-4 (Workspace.Source optional->removed). | Active (Matt, 2026-07-29) | [spawn/despawn §Spawn carries no repo](agent/compass-agent-spawn-despawn/design.md#seam-a--spawn-a-lifecyclecall-family-on-the-agent-gateway-orchestrated-server-side) | -| 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](product/compass-server-ownership-layer-amendment/design.md#what-this-amendment-asserts) | +| 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](server/compass-server-ownership-layer-amendment/design.md#what-this-amendment-asserts) | | 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). RIG-1721 | Active (Matt, 2026-07-31) | [manager comms substrate](agent/compass-manager-comms-substrate/design.md#a1--rig-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 RIG-1622, not part of it. RIG-1722 | Active (Matt, 2026-07-31) | [manager comms substrate](agent/compass-manager-comms-substrate/design.md#a2--rig-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). RIG-1723 | Active (Matt, 2026-07-31) | [manager comms substrate](agent/compass-manager-comms-substrate/design.md#a3--rig-1723-pinned-board--a-pure-pointer-set-over-existing-topic-scoped-messages-edit--topic-mandatory-post--repoint-so-redelivery-is-delivery) | -| DL-182 | 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](product/compass-server-ownership-layer-amendment/design.md#a1--proto-amend-the-t1-add-list-to-the-reconciled-surface) | -| DL-200 | The forge write path is served by a single-method `ForgeCaller` seam (`ExecuteForgeCallAsAccount(ctx, caller, sessionID, call)`) behind `Hub.RelayForgeCall` with the RelayBoardCall guard order (nil-caller CodeUnavailable before resolution; unbound session CodeNotFound; tool errors in-band); the hub keeps only the resolution edge, the oneof dispatch + stamping + provider selection live in the server-side `forgeService` — the first production `forge.StampOwner` caller (DL-050) | Active (Matt, 2026-08-17) | [forge write path §1](product/compass-forge-write-path/design.md#1-the-forgecaller-seam--relayforgecall-server-leg-the-dl-050-chokepoint) | -| DL-201 | The review surface is a `submit_review` arm on the existing `ForgeCallRequest` oneof (verdict + summary body + optional inline path/line/side comments, one POST, never a PENDING review) acked by a `ReviewRef` reference in forge.proto mirroring `CommentRef`; the canonical read-side `compass.v1.Review` is untouched (DL-069/DL-092 hold). The arm executes under a DISTINCT reviewer GitHub identity — a second `server_only` declared secret — so APPROVE/REQUEST_CHANGES/COMMENT are all usable on Compass-authored PRs (GitHub 422s an author's self-review verdicts); the motivating consumer is the `skill://review` loop posting its per-PR review to the PR as that reviewer. Amends DL-052 (the reviewer credential joins the author credential; the Server-holds-write-creds core stands) | Active (Matt, 2026-08-17) | [forge write path §3](product/compass-forge-write-path/design.md#3-github-provider-writes--the-review-surface) | -| DL-202 | Multi-provider addressing is an optional `ForgeRef forge` field on the `ForgeCallRequest` envelope (unset = the configured default GitHub forge — the additive follow-up the standing agent_gateway.proto comment names); there is no capability-negotiation RPC: an op the addressed provider cannot serve returns the in-band `ForgeCallError{code:"unimplemented"}` built on `forge.ErrUnsupported`, and the agent tool prompt documents the static per-provider capability matrix | Active (Matt, 2026-08-17) | [forge write path §4](product/compass-forge-write-path/design.md#4-provider-addressing-same-oneof-envelope-forgeref-typed-degradation) | -| DL-203 | The Linear provider (`go/internal/forge/linear.go`, stdlib GraphQL, team key as `repo`) serves the issues half read+write — the read half served live via the OQ-A untracked-artifact fallback (no Linear ingestion exists) — and returns `ErrUnsupported` for the whole PR/review family — the canonical PullRequest surface is never fabricated on a Linear coordinate; its write credential is its own `server_only` declared secret (DL-052). Amends DL-051 (Linear moves from a deferred issues-only follow-on to in-scope issues read+write; the swappable-`Provider` framing and the PR/review-family-unsupported core stand) | Active (Matt, 2026-08-17) | [forge write path §5](product/compass-forge-write-path/design.md#5-linear-provider-read--write-issues-half) | -| DL-204 | Linear attribution is dual-channel with deliberate granularity: every write passes through `StampOwner` unchanged (the one chokepoint; the header carries the fine-grained PER-AGENT owner truth) AND sets Linear's native `createAsUser` to the ONE general shared Compass app identity (coarse native display, never per-agent; the token is OAuth actor=app, degrading to stamp-only via a named boot-time capability probe on a non-actor token); both values are Server-chosen so DL-050 unforgeability holds on both channels | Active (Matt, 2026-08-17) | [forge write path §5](product/compass-forge-write-path/design.md#5-linear-provider-read--write-issues-half) | -| DL-205 | The DL-055 ownership index materializes as `forge_authored_artifacts` (PK = forge coordinate; agent/owner/session columns; ON DELETE RESTRICT FKs), written by `forgeService` strictly after forge write success — no row for a rejected write, no orphan row on a stamp failure | Active (Matt, 2026-08-17) | [forge write path §6](product/compass-forge-write-path/design.md#6-the-dl-055-ownership-index-first-writer) | -| DL-206 | Forge creates are idempotent under a caller-minted `ForgeCallRequest.client_request_id` whole-chain key (the SpawnPeerRequest.client_request_id precedent, agent_gateway.proto:172), deduped by a memo — a `client_request_id` column + UNIQUE `(agent_account_id, client_request_id)` index on `forge_authored_artifacts`, written in the SAME ordered step as the DL-055 row — so a retried create returns the original artifact, a retry after a failed create re-attempts, and the accepted residual window is exactly the forge-success→pre-commit crash gap | Active (Matt, 2026-08-17) | [forge write path §6](product/compass-forge-write-path/design.md#6-the-dl-055-ownership-index-first-writer) | -| DL-210 | Forge integration testing adds two live-contract tiers above the DL-174 hermetic pyramid: (1) a hermetic golden-fixture replay leg (committed `go/internal/forge/testdata/` fixtures replayed through the stub RoundTripper, an untagged Go test riding moon affected-detection on every forge-affected PR + the main/nightly sweep, no `ci.yml` project enumeration) and (2) a `//go:build livegithub` live-credentials oracle (same scenarios against a throwaway `RigelBuild/compass-forge-testbed` + a Linear test team under test-only bot PATs ≠ the `server_only` production secrets), REQUIRED on forge-affected PRs + nightly, covering GitHub and Linear co-equally (the GitHub-only F1 reviewer≠author 422→APPROVE headline; Linear's PR/review family is `ErrUnsupported`, not a scenario). This is a NEW secret-bearing per-PR CI step — a standalone decision, NOT an amendment: D2's full-stack deterministic tier (`compass-dogfood-e2e/design.md:815-830`) stays secret-free and unchanged; a forward pointer next to D2 records the carve-out. Fork-PR secret safety: the same-repo-head-guarded step goes vacuously green on fork heads (GitHub withholds secrets there); accepted, with the push-to-main run + a maintainer same-repo re-push convention as the catch (0 forks today) | Active (Matt, 2026-08-18) | [forge integration testing §Approach](product/compass-forge-integration-testing/design.md#approach) | -| DL-226 | A message owed to an OFFLINE channel agent member is never silently stranded: the server wakes the member by RESUMING its most recent session (a system-authorized internal sibling of the public resume path, via a new `LatestSessionForAccount` read; fresh start only for a never-started agent; per-agent singleflight + not-live pre-check as cost control — agent-triggered starts are a core product capability, and `StartAgentSession`'s adminOnly PUBLIC door is untouched). Durability split: a mentioned member outside the sweep set (unsubscribed, non-home, non-mandatory) gets a durable owed-mention row `(agent_account_id, message_id)` written at the settle edge before the wake and swept subscription-independently on session start AS A STEER (D5 mention→steer preserved; steer-only precedence intact), cleared on the frozen message_id ack; a subscribed/home/mandatory member gets no owed row — the D2 cursor sweep is its durable backstop and the wake (both the mention arm and the plain-deliver arm) is pure latency. Broadcast mentions (`@everyone`/`@agents`) wake too; N-start amplification accepted. The residual pre-settle window is accepted for MVP (RIG-2490). Amends D5/OQ-3's offline clause by citation | Active (Matt, 2026-08-21); residual pre-settle window closed by DL-240 (RIG-2490, 2026-08-23) | [mention offline redelivery §Decisions](product/compass-mention-offline-redelivery.md#decisions-ruled-by-matt-2026-08-21) | -| DL-240 | The RIG-1641 OQ-5 residual pre-settle mention-loss window (a mention to an offline out-of-sweep-set member, lost when a crash or bus overrun drops `MessagePosted` before the settle-edge `routeMentions` pass) is closed by a per-message delivery marker, NOT a high-water cursor: a nullable `messages.mentions_routed_at BIGINT` (unix ms) set by the consumer after the settle-edge pass, with a recovery scan at consumer start + the `Lagged()` overrun re-subscribe reading `WHERE mentions_routed_at IS NULL` (skipping message_ids in `c.held`), replaying the ONE shared `routeMentions` body, then marking. The killed cursor could advance past a LOWER in-flight seq (`messages.seq` is `BIGSERIAL` assigned at INSERT but the bus event publishes post-commit, `comms.go:361-375`) whose crash-dropped event then stranded the mention forever — a new silent loss the marker cannot express. Seed-forward on first deploy (no historical backfill; compass pre-live). Extends DL-226's no-loss invariant from the settle edge back to the message-post commit; DL-226's wake+owed-mention mechanism is unchanged | Active (Matt, 2026-08-23) | [mention offline pre-settle closure §Resolved decisions](product/compass-mention-offline-redelivery-pre-settle-closure.md#resolved-decisions) | +| DL-182 | 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](server/compass-server-ownership-layer-amendment/design.md#a1--proto-amend-the-t1-add-list-to-the-reconciled-surface) | +| DL-200 | The forge write path is served by a single-method `ForgeCaller` seam (`ExecuteForgeCallAsAccount(ctx, caller, sessionID, call)`) behind `Hub.RelayForgeCall` with the RelayBoardCall guard order (nil-caller CodeUnavailable before resolution; unbound session CodeNotFound; tool errors in-band); the hub keeps only the resolution edge, the oneof dispatch + stamping + provider selection live in the server-side `forgeService` — the first production `forge.StampOwner` caller (DL-050) | Active (Matt, 2026-08-17) | [forge write path §1](server/compass-forge-write-path/design.md#1-the-forgecaller-seam--relayforgecall-server-leg-the-dl-050-chokepoint) | +| DL-201 | The review surface is a `submit_review` arm on the existing `ForgeCallRequest` oneof (verdict + summary body + optional inline path/line/side comments, one POST, never a PENDING review) acked by a `ReviewRef` reference in forge.proto mirroring `CommentRef`; the canonical read-side `compass.v1.Review` is untouched (DL-069/DL-092 hold). The arm executes under a DISTINCT reviewer GitHub identity — a second `server_only` declared secret — so APPROVE/REQUEST_CHANGES/COMMENT are all usable on Compass-authored PRs (GitHub 422s an author's self-review verdicts); the motivating consumer is the `skill://review` loop posting its per-PR review to the PR as that reviewer. Amends DL-052 (the reviewer credential joins the author credential; the Server-holds-write-creds core stands) | Active (Matt, 2026-08-17) | [forge write path §3](server/compass-forge-write-path/design.md#3-github-provider-writes--the-review-surface) | +| DL-202 | Multi-provider addressing is an optional `ForgeRef forge` field on the `ForgeCallRequest` envelope (unset = the configured default GitHub forge — the additive follow-up the standing agent_gateway.proto comment names); there is no capability-negotiation RPC: an op the addressed provider cannot serve returns the in-band `ForgeCallError{code:"unimplemented"}` built on `forge.ErrUnsupported`, and the agent tool prompt documents the static per-provider capability matrix | Active (Matt, 2026-08-17) | [forge write path §4](server/compass-forge-write-path/design.md#4-provider-addressing-same-oneof-envelope-forgeref-typed-degradation) | +| DL-203 | The Linear provider (`go/internal/forge/linear.go`, stdlib GraphQL, team key as `repo`) serves the issues half read+write — the read half served live via the OQ-A untracked-artifact fallback (no Linear ingestion exists) — and returns `ErrUnsupported` for the whole PR/review family — the canonical PullRequest surface is never fabricated on a Linear coordinate; its write credential is its own `server_only` declared secret (DL-052). Amends DL-051 (Linear moves from a deferred issues-only follow-on to in-scope issues read+write; the swappable-`Provider` framing and the PR/review-family-unsupported core stand) | Active (Matt, 2026-08-17) | [forge write path §5](server/compass-forge-write-path/design.md#5-linear-provider-read--write-issues-half) | +| DL-204 | Linear attribution is dual-channel with deliberate granularity: every write passes through `StampOwner` unchanged (the one chokepoint; the header carries the fine-grained PER-AGENT owner truth) AND sets Linear's native `createAsUser` to the ONE general shared Compass app identity (coarse native display, never per-agent; the token is OAuth actor=app, degrading to stamp-only via a named boot-time capability probe on a non-actor token); both values are Server-chosen so DL-050 unforgeability holds on both channels | Active (Matt, 2026-08-17) | [forge write path §5](server/compass-forge-write-path/design.md#5-linear-provider-read--write-issues-half) | +| DL-205 | The DL-055 ownership index materializes as `forge_authored_artifacts` (PK = forge coordinate; agent/owner/session columns; ON DELETE RESTRICT FKs), written by `forgeService` strictly after forge write success — no row for a rejected write, no orphan row on a stamp failure | Active (Matt, 2026-08-17) | [forge write path §6](server/compass-forge-write-path/design.md#6-the-dl-055-ownership-index-first-writer) | +| DL-206 | Forge creates are idempotent under a caller-minted `ForgeCallRequest.client_request_id` whole-chain key (the SpawnPeerRequest.client_request_id precedent, agent_gateway.proto:172), deduped by a memo — a `client_request_id` column + UNIQUE `(agent_account_id, client_request_id)` index on `forge_authored_artifacts`, written in the SAME ordered step as the DL-055 row — so a retried create returns the original artifact, a retry after a failed create re-attempts, and the accepted residual window is exactly the forge-success→pre-commit crash gap | Active (Matt, 2026-08-17) | [forge write path §6](server/compass-forge-write-path/design.md#6-the-dl-055-ownership-index-first-writer) | +| DL-210 | Forge integration testing adds two live-contract tiers above the DL-174 hermetic pyramid: (1) a hermetic golden-fixture replay leg (committed `go/internal/forge/testdata/` fixtures replayed through the stub RoundTripper, an untagged Go test riding moon affected-detection on every forge-affected PR + the main/nightly sweep, no `ci.yml` project enumeration) and (2) a `//go:build livegithub` live-credentials oracle (same scenarios against a throwaway `RigelBuild/compass-forge-testbed` + a Linear test team under test-only bot PATs ≠ the `server_only` production secrets), REQUIRED on forge-affected PRs + nightly, covering GitHub and Linear co-equally (the GitHub-only F1 reviewer≠author 422→APPROVE headline; Linear's PR/review family is `ErrUnsupported`, not a scenario). This is a NEW secret-bearing per-PR CI step — a standalone decision, NOT an amendment: D2's full-stack deterministic tier (`compass-dogfood-e2e/design.md:815-830`) stays secret-free and unchanged; a forward pointer next to D2 records the carve-out. Fork-PR secret safety: the same-repo-head-guarded step goes vacuously green on fork heads (GitHub withholds secrets there); accepted, with the push-to-main run + a maintainer same-repo re-push convention as the catch (0 forks today) | Active (Matt, 2026-08-18) | [forge integration testing §Approach](server/compass-forge-integration-testing/design.md#approach) | +| DL-226 | A message owed to an OFFLINE channel agent member is never silently stranded: the server wakes the member by RESUMING its most recent session (a system-authorized internal sibling of the public resume path, via a new `LatestSessionForAccount` read; fresh start only for a never-started agent; per-agent singleflight + not-live pre-check as cost control — agent-triggered starts are a core product capability, and `StartAgentSession`'s adminOnly PUBLIC door is untouched). Durability split: a mentioned member outside the sweep set (unsubscribed, non-home, non-mandatory) gets a durable owed-mention row `(agent_account_id, message_id)` written at the settle edge before the wake and swept subscription-independently on session start AS A STEER (D5 mention→steer preserved; steer-only precedence intact), cleared on the frozen message_id ack; a subscribed/home/mandatory member gets no owed row — the D2 cursor sweep is its durable backstop and the wake (both the mention arm and the plain-deliver arm) is pure latency. Broadcast mentions (`@everyone`/`@agents`) wake too; N-start amplification accepted. The residual pre-settle window is accepted for MVP (RIG-2490). Amends D5/OQ-3's offline clause by citation | Active (Matt, 2026-08-21); residual pre-settle window closed by DL-240 (RIG-2490, 2026-08-23) | [mention offline redelivery §Decisions](server/compass-mention-offline-redelivery.md#decisions-ruled-by-matt-2026-08-21) | +| DL-240 | The RIG-1641 OQ-5 residual pre-settle mention-loss window (a mention to an offline out-of-sweep-set member, lost when a crash or bus overrun drops `MessagePosted` before the settle-edge `routeMentions` pass) is closed by a per-message delivery marker, NOT a high-water cursor: a nullable `messages.mentions_routed_at BIGINT` (unix ms) set by the consumer after the settle-edge pass, with a recovery scan at consumer start + the `Lagged()` overrun re-subscribe reading `WHERE mentions_routed_at IS NULL` (skipping message_ids in `c.held`), replaying the ONE shared `routeMentions` body, then marking. The killed cursor could advance past a LOWER in-flight seq (`messages.seq` is `BIGSERIAL` assigned at INSERT but the bus event publishes post-commit, `comms.go:361-375`) whose crash-dropped event then stranded the mention forever — a new silent loss the marker cannot express. Seed-forward on first deploy (no historical backfill; compass pre-live). Extends DL-226's no-loss invariant from the settle edge back to the message-post commit; DL-226's wake+owed-mention mechanism is unchanged | Active (Matt, 2026-08-23) | [mention offline pre-settle closure §Resolved decisions](server/compass-mention-offline-redelivery-pre-settle-closure.md#resolved-decisions) | | DL-241 | The agent forge native toolset is ten single-purpose tools, one per `ForgeCallRequest` arm (`forge_get_issue`, `forge_get_pull_request`, `forge_list_issues`, `forge_comment_on_issue`, `forge_comment_on_pull_request`, `forge_submit_review`, `forge_create_issue`, `forge_create_pull_request`, `forge_subscribe`, `forge_unsubscribe`), each a native `AgentTool` over a thin `ForgeBroker` on the `RunnerTransport.forge()` seam; `forge_subscribe`/`forge_unsubscribe` ship the complete surface now but return the server's in-band `unimplemented` until the poll-driver lane lands the `agent_forge_subscriptions` writer (DL-163). Multi-forge is exposed: every tool takes an optional forge selector (`forge_provider` + optional `forge_host`, unset = the configured default GitHub forge, DL-202) so an agent targets Linear (issues-only, `repo` = team key, DL-051; PR/review arms return in-band `unimplemented`) as well as GitHub. Mirrors DL-212's comms-toolset count row; the tool-count claim is load-bearing for future toolset-refresh rows | Active (Matt, 2026-08-24) | [forge tools §Tool set and shape](agent/compass-agent-forge-tools/design.md#tool-set-and-shape--ten-native-tools-one-per-arm) | | DL-291 | Agents and client UIs address channels and topics by NAME, not id — request-input channel/topic fields on the agent tool surface are name-typed, resolved viewer-scoped at the service edge (`ChannelByNameForViewer`: miss ≡ invisible ≡ merged `not_found`; a multi-hit errors `invalid_argument`, never auto-picks); response/stored/event fields stay id-typed. Extends DL-269/DL-270 from accounts to channels + topics. | Active (Matt, 2026-08-30) | [peer-DM §Ledger delta](agent/compass-agent-peer-dm/design.md#ledger-delta) | | DL-292 | Steer/deliver control ops denormalize the source channel name + topic name (server-resolved at wrap in `deliverOp`/`steerOp`, the `from_handle` pattern; a name-resolve miss degrades like a handle miss, never blocks delivery); the agent renders `Channel › topic :` and must name both on every post — `comms_post_message` has NO home-channel default and never auto-picks a reply target. The dogfood reply-mis-routing fix (RIG-2956 T0). Proto: `DeliverControl.channel_name = 5`, `SteerControl.topic_name = 4`/`channel_name = 5`. | Active (Matt, 2026-08-30) | [peer-DM §Ledger delta](agent/compass-agent-peer-dm/design.md#ledger-delta) | @@ -191,11 +191,11 @@ check enforces the mechanical half. Full rationale: | DL-296 | A peer-DM is born `kind=DM`, exactly two agent parties, `post_policy=OPEN` (ownerless), `mandatory_subscription=true`, members = both agents + pulled-in owner(s) (every member a delivery target from birth). A genuine member ADD converts it in the same tx to a named `kind=CHANNEL` (`UpdateChannelMembersRequest.convert_channel_name = 6`, required; leaves the reserved group, freeing the DM name); a remove below two agent parties errors. `CHANNEL_KIND_GROUP_DM = 2` is retired reserve-not-delete (enum number stays, deprecated, never produced; deleting it is buf-breaking). The only kinds are CHANNEL and DM. | Active (Matt, 2026-08-30) | [peer-DM §Ledger delta](agent/compass-agent-peer-dm/design.md#ledger-delta) | | DL-297 | Peer-DM scope is same-owner for MVP (the wave-shares-one-owner frame); a cross-owner peer handle is the merged in-band `not_found`. Cross-owner DMs are deferred to the bilateral owner-peering authz edge (RIG-2796-class). | Active (Matt, 2026-08-30) | [peer-DM §Ledger delta](agent/compass-agent-peer-dm/design.md#ledger-delta) | | DL-276 | The forge rate-limit error surface carries the reset hint so `ForgeCallError.retry_after_ms` (structurally always 0 before this) is populated: a dedicated `*forge.RateLimitError{RetryAfter time.Duration}` in `go/internal/forge/provider.go` REPLACES the bare `ErrBudgetExhausted` sentinel wrap at every emission site and `Unwrap()`s to it, so every existing `errors.Is(err, ErrBudgetExhausted)` site (the poll-driver skip contract, the server `resource_exhausted` arm, all tests) keeps matching while `errors.As` recovers the hint at `mapForgeError`. The value is a `time.Duration` computed CLIENT-side at error construction via the injectable clock (the server has none), so the server conversion to ms is clockless clamped `[0, MaxUint32]` arithmetic; both providers set it (GitHub from `Retry-After`/`X-RateLimit-Reset`, Linear from its rate-limit headers). | Active (Matt, 2026-08-30) | [forge retry-after surface](server/compass-forge-retry-after-surface.md) | -| DL-305 | The forge GitHub credential topology is TWO Apps: the primary App (the existing `ForgeConfig.App`) serves everything — all reads (board ingest + notify, unified per RIG-2991), all author writes, board, and webhooks — and a second reviewer App DEFINITION (own AppID + key, one installation, no webhook) serves only the `submit_review` arm, the F1 author-can't-approve 422 workaround; a second install of one App is rejected (same bot login fails F1), as is a third write App. Enabling writes therefore requires the primary App and force-enables board ingestion — an accepted amendment to Matt's 2026-08-19 independent-gates ruling (`serve.go:150-151`); reads and interactive writes knowingly share the one installation budget + client-side `resetAt` gate | Active (Matt, 2026-08-31) | [forge app credentials](product/compass-forge-app-credentials/design.md) | -| DL-306 | The reviewer App's config is a second `ForgeConfig.ReviewerApp ForgeAppConfig` block mirroring the existing `App` shape — flags `--forge-reviewer-app-id`/`--forge-reviewer-app-installation-id`, declared-secret name `FORGE_REVIEWER_APP_PRIVATE_KEY` (webhook-secret field unused) — never a per-role App-config map, which would sit beside the named-field convention as a second shape | Active (Matt, 2026-08-31) | [forge app credentials](product/compass-forge-app-credentials/design.md) | -| DL-307 | The write-PAT drop is a clean cutover with no fallback: the GitHub write `newForgeTokenSource` usage, the `GITHUB_FORGE_TOKEN`/`GITHUB_FORGE_REVIEWER_TOKEN` secret names, and their flags are deleted outright (no deprecation window), and Linear's write/notify token moves from the member PAT onto ONE shared `linearagent.NewTokenSource` OAuth client_credentials actor=app mint plus a boot-time mint check — finishing DL-204's actor=app intent, not a new decision | Active (Matt, 2026-08-31) | [forge app credentials](product/compass-forge-app-credentials/design.md) | -| DL-308 | The live-test credential model after the PAT drop: the `livegithub` oracle's GitHub legs mint REAL App installation tokens via `forge.NewAppTokenSource` against the testbed Apps (validating the production mint path), while Linear retains exactly ONE user credential (`LINEAR_FORGE_USER_TOKEN`) used only for the human→app delegation setup the app-actor cannot self-assign; the Linear write/notify legs stay on the minted app token | Active (Matt, 2026-08-31) | [forge app credentials](product/compass-forge-app-credentials/design.md) | -| DL-309 | Real-App webhook validation is a LIVE TUNNEL ROUND-TRIP in the livegithub tier: a smee.io-style tunnel receiver gives the oracle a public ingress, the real GitHub App and real Linear app webhooks register against the tunnel, and one real delivery per provider is asserted end to end through the mounted handlers (`NewGitHubWebhookHandler`/`NewLinearWebhookHandler`) with fail-closed signature verify — chosen over capture-and-replay, accepting the live-ingress machinery in CI; the one-time registration + PEM-rotation runbook stays | Active (Matt, 2026-08-31) | [forge app credentials](product/compass-forge-app-credentials/design.md) | +| DL-305 | The forge GitHub credential topology is TWO Apps: the primary App (the existing `ForgeConfig.App`) serves everything — all reads (board ingest + notify, unified per RIG-2991), all author writes, board, and webhooks — and a second reviewer App DEFINITION (own AppID + key, one installation, no webhook) serves only the `submit_review` arm, the F1 author-can't-approve 422 workaround; a second install of one App is rejected (same bot login fails F1), as is a third write App. Enabling writes therefore requires the primary App and force-enables board ingestion — an accepted amendment to Matt's 2026-08-19 independent-gates ruling (`serve.go:150-151`); reads and interactive writes knowingly share the one installation budget + client-side `resetAt` gate | Active (Matt, 2026-08-31) | [forge app credentials](server/compass-forge-app-credentials/design.md) | +| DL-306 | The reviewer App's config is a second `ForgeConfig.ReviewerApp ForgeAppConfig` block mirroring the existing `App` shape — flags `--forge-reviewer-app-id`/`--forge-reviewer-app-installation-id`, declared-secret name `FORGE_REVIEWER_APP_PRIVATE_KEY` (webhook-secret field unused) — never a per-role App-config map, which would sit beside the named-field convention as a second shape | Active (Matt, 2026-08-31) | [forge app credentials](server/compass-forge-app-credentials/design.md) | +| DL-307 | The write-PAT drop is a clean cutover with no fallback: the GitHub write `newForgeTokenSource` usage, the `GITHUB_FORGE_TOKEN`/`GITHUB_FORGE_REVIEWER_TOKEN` secret names, and their flags are deleted outright (no deprecation window), and Linear's write/notify token moves from the member PAT onto ONE shared `linearagent.NewTokenSource` OAuth client_credentials actor=app mint plus a boot-time mint check — finishing DL-204's actor=app intent, not a new decision | Active (Matt, 2026-08-31) | [forge app credentials](server/compass-forge-app-credentials/design.md) | +| DL-308 | The live-test credential model after the PAT drop: the `livegithub` oracle's GitHub legs mint REAL App installation tokens via `forge.NewAppTokenSource` against the testbed Apps (validating the production mint path), while Linear retains exactly ONE user credential (`LINEAR_FORGE_USER_TOKEN`) used only for the human→app delegation setup the app-actor cannot self-assign; the Linear write/notify legs stay on the minted app token | Active (Matt, 2026-08-31) | [forge app credentials](server/compass-forge-app-credentials/design.md) | +| DL-309 | Real-App webhook validation is a LIVE TUNNEL ROUND-TRIP in the livegithub tier: a smee.io-style tunnel receiver gives the oracle a public ingress, the real GitHub App and real Linear app webhooks register against the tunnel, and one real delivery per provider is asserted end to end through the mounted handlers (`NewGitHubWebhookHandler`/`NewLinearWebhookHandler`) with fail-closed signature verify — chosen over capture-and-replay, accepting the live-ingress machinery in CI; the one-time registration + PEM-rotation runbook stays | Active (Matt, 2026-08-31) | [forge app credentials](server/compass-forge-app-credentials/design.md) | ## Agent roles & prompts @@ -221,19 +221,19 @@ check enforces the mechanical half. Full rationale: | DL-035 | The bottom dock is a tabbed Dispatcher + Warden surface | Superseded by DL-036 (Matt, 2026-07-11) | [ADE shell §D7](product/compass-ade-shell/design.md#d7--bottom-dock-is-tabbed-dispatcher--warden) | | DL-036 | The bottom dock is folded into the right sidebar: one activity bar, one signal, the dock removed | Active (Matt, 2026-07-11) | [dock-in-sidebar §D1](product/compass-dock-in-sidebar/design.md#d1--one-activity-bar-one-signal-the-dock-state-is-removed) | | DL-037 | Standalone channel asks are answerable wherever they are asked (first-responder-wins), not read-only-deferred | Active (Matt, 2026-07-20) | [ask-in-channel §The designed change](agent/compass-ask-in-channel/design.md#the-designed-change) | -| DL-067 | The `workstream` concept is removed from the Compass UI entirely; the board unit becomes the canonical Compass `Issue` (and `PullRequest`) — the forge artifact's fields plus the Compass machinery (lifecycle state, priority, assignee, tracker projection), server-computed | Active (Matt, 2026-07-30) | [issue model §Approach](product/compass-issue-model/design.md#approach) | -| DL-068 | Agent attribution on issues and PRs is consumed from the canonical type's server-parsed `agent`/owner fields (parsed at ingestion per #995's stamping) and rendered as an untrusted claim, hedged unless the server's forge-login cross-check verifies it (DL-050, #995 OQ-1); the UI never stamps a header and never derives `assignee` (Compass truth) from `agent` (a parsed claim) | Superseded by DL-094 (Matt, 2026-08-01) | [issue model §Attribution](product/compass-issue-model/design.md#attribution-consumed-never-stamped) | -| DL-069 | Compass owns a single canonical `compass.v1` `Issue`/`PullRequest` type pair (forge fields + agent author/owner attribution + Compass machinery); the server translates raw forge data into these types at ingestion, the raw forge shape is never a proto/wire type, and the UI consumes only the generated canonical type from `@compass/client` | Active (Matt, 2026-07-31) | [issue model §The Compass Issue and PullRequest types](product/compass-issue-model/design.md#the-compass-issue-and-pullrequest-types) | -| DL-070 | The DL-033 issue lifecycle (its seven working states unchanged, extended by a terminal `ARCHIVED` state per DL-091) is server-authoritative: a server-side board projection — composing the existing `go/internal/board` recorded-state pattern and DL-055's board-as-local-index-scan — computes and streams the canonical type, moving DL-032's canonical Compass state server-side | Active (Matt, 2026-07-30) | [issue model §The server projection](product/compass-issue-model/design.md#the-server-projection) | -| DL-091 | Archiving a Compass issue is a lifecycle transition to a terminal `ARCHIVED` state via `UpdateIssueState`, not a separate `archived_at` marker field or a separate `ArchiveIssue` RPC; an archived issue drops off the active board and is listed in the Done view's Archived section, and every board item carries a forge identity (`ForgeRef` provider+host) so multi-forge artifacts never collide on `repo` alone | Superseded by DL-129 (Matt, 2026-08-04) | [issue model §The write path](product/compass-issue-model/design.md#the-write-path) | -| DL-094 | Compass agent attribution is a plain display fact, not a trust claim: `AgentAttribution` slims to `{agent_handle}` (field numbers 2–3 and names `owner_handle`/`verified` reserved), populated directly from the parsed owner header at ingestion with no forge-login cross-check and no population gating; the board and the forge-tool path both render the bare `@handle` with no hedge; owner is an account property (`AgentAccount.owner_user_id`), resolved server-side, never restated per artifact. Reverses DL-068's hedge-unless-verified board render and the verified/cross-check of DL-050 and #995 OQ-1 as applied to attribution display; routes around #1018/#1037 (unedited; no wire build shipped) | Superseded by DL-186 (Matt, 2026-08-07) | [attribution simplification §Approach](product/compass-attribution-simplification/design.md#approach) | +| DL-067 | The `workstream` concept is removed from the Compass UI entirely; the board unit becomes the canonical Compass `Issue` (and `PullRequest`) — the forge artifact's fields plus the Compass machinery (lifecycle state, priority, assignee, tracker projection), server-computed | Active (Matt, 2026-07-30) | [issue model §Approach](server/compass-issue-model/design.md#approach) | +| DL-068 | Agent attribution on issues and PRs is consumed from the canonical type's server-parsed `agent`/owner fields (parsed at ingestion per #995's stamping) and rendered as an untrusted claim, hedged unless the server's forge-login cross-check verifies it (DL-050, #995 OQ-1); the UI never stamps a header and never derives `assignee` (Compass truth) from `agent` (a parsed claim) | Superseded by DL-094 (Matt, 2026-08-01) | [issue model §Attribution](server/compass-issue-model/design.md#attribution-consumed-never-stamped) | +| DL-069 | Compass owns a single canonical `compass.v1` `Issue`/`PullRequest` type pair (forge fields + agent author/owner attribution + Compass machinery); the server translates raw forge data into these types at ingestion, the raw forge shape is never a proto/wire type, and the UI consumes only the generated canonical type from `@compass/client` | Active (Matt, 2026-07-31) | [issue model §The Compass Issue and PullRequest types](server/compass-issue-model/design.md#the-compass-issue-and-pullrequest-types) | +| DL-070 | The DL-033 issue lifecycle (its seven working states unchanged, extended by a terminal `ARCHIVED` state per DL-091) is server-authoritative: a server-side board projection — composing the existing `go/internal/board` recorded-state pattern and DL-055's board-as-local-index-scan — computes and streams the canonical type, moving DL-032's canonical Compass state server-side | Active (Matt, 2026-07-30) | [issue model §The server projection](server/compass-issue-model/design.md#the-server-projection) | +| DL-091 | Archiving a Compass issue is a lifecycle transition to a terminal `ARCHIVED` state via `UpdateIssueState`, not a separate `archived_at` marker field or a separate `ArchiveIssue` RPC; an archived issue drops off the active board and is listed in the Done view's Archived section, and every board item carries a forge identity (`ForgeRef` provider+host) so multi-forge artifacts never collide on `repo` alone | Superseded by DL-129 (Matt, 2026-08-04) | [issue model §The write path](server/compass-issue-model/design.md#the-write-path) | +| DL-094 | Compass agent attribution is a plain display fact, not a trust claim: `AgentAttribution` slims to `{agent_handle}` (field numbers 2–3 and names `owner_handle`/`verified` reserved), populated directly from the parsed owner header at ingestion with no forge-login cross-check and no population gating; the board and the forge-tool path both render the bare `@handle` with no hedge; owner is an account property (`AgentAccount.owner_user_id`), resolved server-side, never restated per artifact. Reverses DL-068's hedge-unless-verified board render and the verified/cross-check of DL-050 and #995 OQ-1 as applied to attribution display; routes around #1018/#1037 (unedited; no wire build shipped) | Superseded by DL-186 (Matt, 2026-08-07) | [attribution simplification §Approach](server/compass-attribution-simplification/design.md#approach) | | DL-096 | Sidebar pinning is a user-configurable presentation layer over the agent set (the layer DL-095 names and defers): any agent can be pinned as a right-sidebar fleet tab, the hardcoded always-on Supervisor/Warden tabs and their `FleetTab` union literals are removed (no built-in privileged agent), the pin set is a per-user client-local UI preference (`localStorage`-backed; never DL-078's fleet-wide bundle; server per-user prefs a post-MVP seam), the default pin set is EMPTY, and the shell boots onto the first pin that resolves to a visible agent, else Status, instead of a special-cased Supervisor conversation | Active (Matt, 2026-08-01) | [sidebar pins §Approach](product/compass-sidebar-pins/design.md#approach) | | DL-097 | The Bridge board splits into peer Issues/PRs tabs inside the board view (a Bridge-local tab signal, not a new `View` or a third `BoardMode`): the Issues tab is today's board ordered by Record C's `treeOrder`; the PRs tab is a flat one-row-per-OPEN-PR list (the `primaryPr` compression deliberately does not apply) grouped by assignee in `treeOrder` with the tab badge = the open-PR row count; issues ↔ PRs cross-link through the existing issue-keyed selection (`selectIssue`, no new PR-selection state); and the card's per-check pip strip collapses to a CI badge (the `ChecksSummary.state` roll-up) + a review badge (latest-per-author verdict, `changes_requested` > `approved` > `commented`) as pure `board-render.ts` helpers — a VIEW remodel consuming the frozen #1018 model, no proto/server/model change | Active (Matt, 2026-08-01) / PRs-tab flat-list clause superseded by DL-196 (Matt, 2026-08-16) | [board view §Approach](product/compass-board-view/design.md#approach) | | DL-129 | The issue-state write model is agent-primary: the Compass UI is read-only for lifecycle state; agents set state via a sibling `BoardCall*` family on the `AgentGateway` socket relayed by `RelayBoardCall` (DL-049/DL-076 convention), and users set state in the external tracker, whose native status is ingested into the DL-070 server projection through the reverse `TrackerStatusMapping` (DL-053 poll; echo-suppressed in tracker-status space, tracker-sourced transitions never mirror back, stale polls dropped by a recency guard). All issues flow from the tracker (no Compass-native/unlinked issues in the MVP); `ARCHIVED` is not agent-only (a Done issue auto-transitions to `ARCHIVED` after 24h, a user reopen un-archives via ingestion); MVP write-authz is single-trust-domain (hierarchical scope is a filed follow-up over DL-095's tree). `UpdateIssueState` on `CompassService` is never built (no wire build shipped). Supersedes DL-091 (the `UpdateIssueState`-on-`CompassService` mechanism; archive-as-transition survives here) and DL-032 (the tracker is now also a state write source); DL-070/DL-033/DL-067 unchanged | Active (Matt, 2026-08-04) | [agent-primary lifecycle §Approach](agent/compass-agent-primary-lifecycle/design.md#approach) | | DL-113 | An unresolvable pinned agent does not vanish: the pin keeps its activity-bar item, its pane renders an "agent unreachable" state, and removal is a manual unpin only — a visibility fluctuation to unresolvable never auto-switches the active tab to `status` (the unpin-active→status fallback is retained, and the pane arm closes the Switch's no-default-arm gap) — amending DL-096's unresolvable-pin handling (the frozen record's §T2 filter and §T3 fluctuation-coercion clauses are superseded; DL-096's pinning-layer core stands) | Active (Matt, 2026-08-02) | [unreachable-pin amendment §Approach](product/compass-sidebar-pins-unreachable-amendment/design.md#approach) | | DL-127 | The Compass UI shell adopts `@solidjs/router` in `HashRouter` mode (Matt's library ruling) emitting the frozen `#/channel//topic/` route shape for a client-only SPA in the Wails v3 webview (DL-110); and the URL becomes the source of truth for view + routed selection — the store derives `View` and routed memos from the matched route (route drives store, not store drives route), with `MemoryRouter` for deterministic tests | Active (Matt, 2026-08-03) | [shell routing §A2](product/compass-shell-routing/design.md#a2--source-of-truth-url-drives-the-store-decision) | | DL-128 | The Compass UI adopts `@tanstack/solid-query` + `@connectrpc/connect-query-core` for server-state: one app-lifetime `QueryClient` created beside the store singleton in `index.tsx` and provided via the SolidJS `QueryClientProvider` (Solid adapter, never React); server-state reads move off the store onto query hooks over the generated Connect clients, the store keeping only client/UI state | Active (Matt, 2026-08-03) | [query layer §Approach](product/compass-ui-query-layer/design.md#approach) | -| DL-186 | Pre-dogfood proto wire-compat is stripped: all `reserved` markers removed across compass/v1, live fields densely renumbered (no holes), and the `buf breaking` CI gate removed (re-armed at GA / first pinned client). Reclaims AgentAttribution field numbers 2,3 (DL-094's forever-reservation) — DL-094's attribution-as-display-fact semantics are UNCHANGED (owner resolved on AgentAccount.owner_user_id, not per-artifact); only its wire-number reservation is dropped, safe because zero clients are deployed and nothing on disk is proto-encoded. | Active (Matt, 2026-08-07) | [attribution simplification §Approach](product/compass-attribution-simplification/design.md#approach) | +| DL-186 | Pre-dogfood proto wire-compat is stripped: all `reserved` markers removed across compass/v1, live fields densely renumbered (no holes), and the `buf breaking` CI gate removed (re-armed at GA / first pinned client). Reclaims AgentAttribution field numbers 2,3 (DL-094's forever-reservation) — DL-094's attribution-as-display-fact semantics are UNCHANGED (owner resolved on AgentAccount.owner_user_id, not per-artifact); only its wire-number reservation is dropped, safe because zero clients are deployed and nothing on disk is proto-encoded. | Active (Matt, 2026-08-07) | [attribution simplification §Approach](server/compass-attribution-simplification/design.md#approach) | | DL-193 | The Compass UI's live agent roster is a reactive JOIN, not a `GetRoster`-as-roster replace: durable identity + tree come from the already-live `SubscribeComms` accounts (the single identity source — `RosterEntry` lacks `homeChannelId`/`kind` the UI requires), while ephemeral presence + activity come from a new `CommsState.presence` map seeded by one `GetRoster(scope: OWNER)` per snapshot boundary and tailed by the `AgentPresenceChanged` event the stream driver currently drops; `STUB_AGENTS` is demoted from live render source to the offline-store seed behind the store's reactive `agents` accessor (the `STUB_ISSUES` seam pattern), never rendered on a live connection | Active (Matt, 2026-08-16) | [live roster §Resolved decisions](product/compass-live-roster/design.md#resolved-decisions) | | DL-194 | The presence→dot projection is the total 4-state mapping `WORKING→working, IDLE→idle, WAITING→waiting, OFFLINE→stopped` plus a defensive `UNSPECIFIED→undefined` (unreachable on the `GetRoster` path, which defaults every agent absent from its in-memory presence source to `OFFLINE`; the client `joinAgents` mirrors this at its own seam, mapping a presence-map miss — a snapshot-boundary race or a post-snapshot `accountChanged` arrival — to `stopped` rather than the components' `?? "idle"` fallback, so the absent→stopped invariant holds end-to-end); the 4-state enum cannot distinguish a deliberately-stopped agent from a never-started one, so a freshly-seeded-but-unstarted agent renders the "stopped" dot — accepted as a known day-one cost, with the terminated/never-started split owed to the deferred `AgentSessionStatus` lane, which is also the only source for the remaining four `AgentState` values (`done/paused/error/disconnected`) | Active (Matt, 2026-08-16) | [live roster §Resolved decisions](product/compass-live-roster/design.md#resolved-decisions) | | DL-195 | `Agent.role`/`model`/`cwd` are optional view-model fields with no server source: a live agent renders without them (the role pip is render-gated on a present, non-worker role — never derived from tree position, since "has children" is not "supervisor"), and `terminals` is `[]` for live agents until a terminal stream exists | Active (Matt, 2026-08-16) | [live roster §Resolved decisions](product/compass-live-roster/design.md#resolved-decisions) | @@ -254,8 +254,8 @@ check enforces the mechanical half. Full rationale: | DL-039 | Compass builds a first-party typed session renderer; session events cross a typed gRPC stream, not opaque bytes and explicitly not ACP | Active (Matt, 2026-07-20) | [architecture lineage](meta/compass-architecture-lineage/design.md) | | DL-040 | Threaded replies use a Slack-style side-thread panel keyed by a deterministic root id | Superseded by DL-098 (Matt, 2026-08-02) | [architecture lineage](meta/compass-architecture-lineage/design.md) | | DL-041 | The message surface is a virtualized thread list with streaming markdown (solid-markdown + Shiki), mention-first composition | Superseded by DL-098 (Matt, 2026-08-02) | [message surface rendering §Approach](product/compass-message-surface-rendering/design.md#approach) | -| DL-098 | Compass adopts Zulip's threading model: a channel is a collection of named topics and every message belongs to exactly one topic (`messages.topic_id NOT NULL`) and stores only that topic id — never a channel id; the channel is reached through `topics.channel_id` (so moving a topic between channels is a single-row update). `parent_message_id` and the message's channel container are removed outright (pre-dogfood: no reserved tombstone, field numbers reused). Topics are first-class (proto `Topic` + `topics` table), named, id-stable, in exactly one channel; posting is topic-mandatory via `topic_name` get-or-create (one call); there is no default, general, or catch-all topic anywhere — channels carry zero messages directly. Rename-to-existing merges topics; the delivery cursor stays per-(agent,channel) with topic as deliver-op metadata, its channel resolved through the topic join (clarifies DL-072). The pre-dogfood SQL migrations collapse to a single baseline (no data conversion). UI is Zulip two-level drill-in: a channel shows its topic index (no composer), a topic shows its messages + composer (no nested threading), the left sidebar lists a channel's recent topics. Reverses DL-040 (Slack side-thread panel) and DL-041 (virtualized thread list, re-pointed at the topic list) | Active (Matt, 2026-08-02) | [Zulip threading model §Decisions](product/compass-zulip-threading-model/design.md#decisions-ratified) | -| DL-099 | An agent's streamed turn does not write to comms at all: the RIG-1364 T3 conversation write-through (streamed-turn → home-channel comms auto-post) is removed. A comms `Message` is created only by an explicit `comms_post_message(topic)` call (agents) or the human client's PostMessage — the comms tool is the only agent comms-write path, and it carries a mandatory topic. The streamed turn survives in full on the session/trace surface (`SessionFrame.typed_event`). Structural enforcement of on-topic posting: an agent cannot flood a channel by talking, only by deliberately posting | Active (Matt, 2026-08-02) | [Zulip threading model §D7](product/compass-zulip-threading-model/design.md#d7--one-comms-write-path-the-streaming-write-through-is-removed-supersedes-rig-1364-t3) | +| DL-098 | Compass adopts Zulip's threading model: a channel is a collection of named topics and every message belongs to exactly one topic (`messages.topic_id NOT NULL`) and stores only that topic id — never a channel id; the channel is reached through `topics.channel_id` (so moving a topic between channels is a single-row update). `parent_message_id` and the message's channel container are removed outright (pre-dogfood: no reserved tombstone, field numbers reused). Topics are first-class (proto `Topic` + `topics` table), named, id-stable, in exactly one channel; posting is topic-mandatory via `topic_name` get-or-create (one call); there is no default, general, or catch-all topic anywhere — channels carry zero messages directly. Rename-to-existing merges topics; the delivery cursor stays per-(agent,channel) with topic as deliver-op metadata, its channel resolved through the topic join (clarifies DL-072). The pre-dogfood SQL migrations collapse to a single baseline (no data conversion). UI is Zulip two-level drill-in: a channel shows its topic index (no composer), a topic shows its messages + composer (no nested threading), the left sidebar lists a channel's recent topics. Reverses DL-040 (Slack side-thread panel) and DL-041 (virtualized thread list, re-pointed at the topic list) | Active (Matt, 2026-08-02) | [Zulip threading model §Decisions](server/compass-zulip-threading-model/design.md#decisions-ratified) | +| DL-099 | An agent's streamed turn does not write to comms at all: the RIG-1364 T3 conversation write-through (streamed-turn → home-channel comms auto-post) is removed. A comms `Message` is created only by an explicit `comms_post_message(topic)` call (agents) or the human client's PostMessage — the comms tool is the only agent comms-write path, and it carries a mandatory topic. The streamed turn survives in full on the session/trace surface (`SessionFrame.typed_event`). Structural enforcement of on-topic posting: an agent cannot flood a channel by talking, only by deliberately posting | Active (Matt, 2026-08-02) | [Zulip threading model §D7](server/compass-zulip-threading-model/design.md#d7--one-comms-write-path-the-streaming-write-through-is-removed-supersedes-rig-1364-t3) | ## Ask contract @@ -395,11 +395,11 @@ check enforces the mechanical half. Full rationale: | ID | Decision | Status | Record | | --- | --- | --- | --- | -| DL-254 | The Linear Agent Session responder is a plain `POST /webhooks` `http.Handler` mounted on the compass-server network TLS door (inside `buildNetworkServer`, beside the Connect mounts, inheriting the G112/RIG-1298 guards; NOT a Connect service, NOT a dedicated ingress), fail-closed on the raw-body HMAC-SHA256 `Linear-Signature` check (bad/missing signature → 400; a stale-but-validly-signed `webhookTimestamp` is 200-with-drop, never a retry-burning 400), acking 200 before any work (Linear's 5s SLA) with all agent work async; the public base URL (webhook host + deep-link base) is a per-deployment config value, never hardcoded | Active (Matt, 2026-08-25) | [linear agent responder §Part 1](product/compass-linear-agent-responder/design.md#part-1--the-webhook-receiver-on-the-network-door) | -| DL-255 | A delegated Linear session is routed to a stable Compass Manager keyed on Compass's recorded forge ownership index (`forge_authored_artifacts`, DL-055/DL-205) — NEVER a header parsed from forge text (DL-050/DL-094 forbid it reaching a routing decision); an issue with no recorded ownership row routes to the supervisor/top-level Manager via a dedicated routing channel, which decides the lane and stamps it through the DL-050 write chokepoint so later events resolve directly | Active (Matt, 2026-08-25) | [linear agent responder §Part 2](product/compass-linear-agent-responder/design.md#part-2--routing-a-delegated-linear-session-to-a-stable-manager) | -| DL-256 | The Linear return path is a dumb link (Option B, Matt 2026-08-25): on `created` the responder emits one `thought` plus an `externalUrls` "Open in Compass" deep link to the resolved Manager's home channel and nothing else — NO activity relay, NO settle observation, NO Linear session-lifecycle machine. One Linear session is NOT forced 1-1 to a comms topic; the prompt lands in an issue-named topic (persisted in a new `linear_agent_sessions` table) delivered as `@linear`-authored deliver-rail messages deduped by `PostAsAccount`'s `client_request_id` on the `Linear-Delivery` UUID, but the deep link targets the home channel. Option C (coarse one-way status) is the ratified follow-up; Option A (full bidirectional relay) is off-table until Linear's activity vocabulary is configurable | Active (Matt, 2026-08-25) | [linear agent responder §Part 3](product/compass-linear-agent-responder/design.md#part-3--the-return-path-a-dumb-link-option-b) | -| DL-268 | The Linear return path's `externalUrls` link is a SERVER-RESOLVED INDIRECTION URL (Matt, RIG-2794), amending DL-256's link target only: on `created` the responder sets the session external URL ONCE, immediately, to the stable post-independent `https:///l/session/` (fits the 10s SLA; still exactly one emit per session, preserving DL-256's dumb link while removing its staleness failure mode), and a new read-only route `GET /l/session/` on the same DL-254 network TLS door reads the `linear_agent_sessions` association (for the issue coordinate only, never the stale created-time target) + resolves the DL-055 ownership index at CLICK time and 302s to the current best target (the dedicated routing channel while unrouted or issue-less, the resolved Manager's home channel once an ownership row exists); nothing rewrites the Linear session. All other DL-256 invariants survive (dumb link, no relay, no settle observation, no session-lifecycle machine, not-1-1 topic mapping). The #646 direct-link stage ships as-is; the resolver is the follow-on | Active (Matt, 2026-08-26) | [return-path indirection amendment §Approach](product/compass-linear-return-path-indirection-amendment/design.md#approach) | -| DL-302 | The Linear webhook mount is `POST /webhooks/linear` (NOT bare `/webhooks`) and is LANDED BY THE FORGE NOTIFICATION LANE (RIG-2732), amending DL-254's path + mount-ownership only (Matt, RIG-2717, 2026-08-30): one shared Linear `http.Handler` on the same DL-254 network TLS door does one fail-closed raw-body HMAC verify then an inner `type`-switch — `AgentSessionEvent` → the RIG-2717 dispatcher via `linearagent.Dispatcher.Enqueue` (full → `ErrQueueFull` → HTTP 500 so Linear retries), `Issue`/`Comment` data-change → RIG-2732's notify/board arms; provider-suffixed for symmetry with `/webhooks/github` (RIG-2883), the bare path being a pre-GitHub-webhook artifact. RIG-2717's residual T7 shrinks to dispatcher ASSEMBLY only (construct + wire seams + start `Dispatcher.Run` on the serve errgroup + expose `Enqueue`); it mounts nothing. T8 e2e stays RIG-2717's, driven through the shared mount. The per-deployment Linear App webhook URL registers at `/webhooks/linear` (a manual console step already required). All other DL-254 invariants survive (network-door placement, fail-closed HMAC, ack-200-before-work, per-deployment public base URL) | Active (Matt, 2026-08-30) | [linear webhook mount amendment §Approach](product/compass-linear-webhook-mount-amendment/design.md#approach) | +| DL-254 | The Linear Agent Session responder is a plain `POST /webhooks` `http.Handler` mounted on the compass-server network TLS door (inside `buildNetworkServer`, beside the Connect mounts, inheriting the G112/RIG-1298 guards; NOT a Connect service, NOT a dedicated ingress), fail-closed on the raw-body HMAC-SHA256 `Linear-Signature` check (bad/missing signature → 400; a stale-but-validly-signed `webhookTimestamp` is 200-with-drop, never a retry-burning 400), acking 200 before any work (Linear's 5s SLA) with all agent work async; the public base URL (webhook host + deep-link base) is a per-deployment config value, never hardcoded | Active (Matt, 2026-08-25) | [linear agent responder §Part 1](server/compass-linear-agent-responder/design.md#part-1--the-webhook-receiver-on-the-network-door) | +| DL-255 | A delegated Linear session is routed to a stable Compass Manager keyed on Compass's recorded forge ownership index (`forge_authored_artifacts`, DL-055/DL-205) — NEVER a header parsed from forge text (DL-050/DL-094 forbid it reaching a routing decision); an issue with no recorded ownership row routes to the supervisor/top-level Manager via a dedicated routing channel, which decides the lane and stamps it through the DL-050 write chokepoint so later events resolve directly | Active (Matt, 2026-08-25) | [linear agent responder §Part 2](server/compass-linear-agent-responder/design.md#part-2--routing-a-delegated-linear-session-to-a-stable-manager) | +| DL-256 | The Linear return path is a dumb link (Option B, Matt 2026-08-25): on `created` the responder emits one `thought` plus an `externalUrls` "Open in Compass" deep link to the resolved Manager's home channel and nothing else — NO activity relay, NO settle observation, NO Linear session-lifecycle machine. One Linear session is NOT forced 1-1 to a comms topic; the prompt lands in an issue-named topic (persisted in a new `linear_agent_sessions` table) delivered as `@linear`-authored deliver-rail messages deduped by `PostAsAccount`'s `client_request_id` on the `Linear-Delivery` UUID, but the deep link targets the home channel. Option C (coarse one-way status) is the ratified follow-up; Option A (full bidirectional relay) is off-table until Linear's activity vocabulary is configurable | Active (Matt, 2026-08-25) | [linear agent responder §Part 3](server/compass-linear-agent-responder/design.md#part-3--the-return-path-a-dumb-link-option-b) | +| DL-268 | The Linear return path's `externalUrls` link is a SERVER-RESOLVED INDIRECTION URL (Matt, RIG-2794), amending DL-256's link target only: on `created` the responder sets the session external URL ONCE, immediately, to the stable post-independent `https:///l/session/` (fits the 10s SLA; still exactly one emit per session, preserving DL-256's dumb link while removing its staleness failure mode), and a new read-only route `GET /l/session/` on the same DL-254 network TLS door reads the `linear_agent_sessions` association (for the issue coordinate only, never the stale created-time target) + resolves the DL-055 ownership index at CLICK time and 302s to the current best target (the dedicated routing channel while unrouted or issue-less, the resolved Manager's home channel once an ownership row exists); nothing rewrites the Linear session. All other DL-256 invariants survive (dumb link, no relay, no settle observation, no session-lifecycle machine, not-1-1 topic mapping). The #646 direct-link stage ships as-is; the resolver is the follow-on | Active (Matt, 2026-08-26) | [return-path indirection amendment §Approach](server/compass-linear-return-path-indirection-amendment/design.md#approach) | +| DL-302 | The Linear webhook mount is `POST /webhooks/linear` (NOT bare `/webhooks`) and is LANDED BY THE FORGE NOTIFICATION LANE (RIG-2732), amending DL-254's path + mount-ownership only (Matt, RIG-2717, 2026-08-30): one shared Linear `http.Handler` on the same DL-254 network TLS door does one fail-closed raw-body HMAC verify then an inner `type`-switch — `AgentSessionEvent` → the RIG-2717 dispatcher via `linearagent.Dispatcher.Enqueue` (full → `ErrQueueFull` → HTTP 500 so Linear retries), `Issue`/`Comment` data-change → RIG-2732's notify/board arms; provider-suffixed for symmetry with `/webhooks/github` (RIG-2883), the bare path being a pre-GitHub-webhook artifact. RIG-2717's residual T7 shrinks to dispatcher ASSEMBLY only (construct + wire seams + start `Dispatcher.Run` on the serve errgroup + expose `Enqueue`); it mounts nothing. T8 e2e stays RIG-2717's, driven through the shared mount. The per-deployment Linear App webhook URL registers at `/webhooks/linear` (a manual console step already required). All other DL-254 invariants survive (network-door placement, fail-closed HMAC, ack-200-before-work, per-deployment public base URL) | Active (Matt, 2026-08-30) | [linear webhook mount amendment §Approach](server/compass-linear-webhook-mount-amendment/design.md#approach) | ## Infrastructure & CI diff --git a/docs/designs/agent/compass-agent-forge-tools/design.md b/docs/designs/agent/compass-agent-forge-tools/design.md index 902043464..c88113ed3 100644 --- a/docs/designs/agent/compass-agent-forge-tools/design.md +++ b/docs/designs/agent/compass-agent-forge-tools/design.md @@ -16,7 +16,7 @@ its T3) and the lifecycle tools (`packages/compass-agent/src/lifecycle.ts`): same broker shape, same identity model, same registration path. Everything below the agent — the Server write chokepoint, the hub relay, the Runner gateway leg, and the proto carrier — is COMPLETE and frozen elsewhere -([`compass-forge-write-path/design.md`](../../product/compass-forge-write-path/design.md)); +([`compass-forge-write-path/design.md`](../../server/compass-forge-write-path/design.md)); this record consumes it and designs nothing on the Go side. **Grounding.** This record and the code it describes live together in this @@ -72,7 +72,7 @@ else. in-band `unimplemented` until the writer lands.** The backend arms are `CodeUnimplemented` stubs (`go/server/forge.go`) pending the `agent_forge_subscriptions` store writer, which the poll-driver lane owns - ([`compass-forge-poll-driver/design.md`](../../product/compass-forge-poll-driver/design.md), + ([`compass-forge-poll-driver/design.md`](../../server/compass-forge-poll-driver/design.md), DL-163: the tables land writer-less; that lane brings the writer). Matt ruled build-all: the tools ship now so the surface is stable and complete; the two arms simply error at runtime (rendered as a clean in-band @@ -362,7 +362,7 @@ hard scope enforcement is A8's frozen deferral, not reopened here. churn), and the runtime error is honest — the per-tool guidance states the arm is not yet wired, so the model learns "not available yet", not "broken". This composes with the poll-driver lane - ([`compass-forge-poll-driver/design.md`](../../product/compass-forge-poll-driver/design.md), + ([`compass-forge-poll-driver/design.md`](../../server/compass-forge-poll-driver/design.md), DL-163: the tables land writer-less; that lane brings the writer). - **Deferring the `ForgeRef` (provider/host) selector.** Considered and OVERRULED by Matt: expose it now. The wave needs BOTH forges — it files diff --git a/docs/designs/agent/compass-agent-peer-dm/design.md b/docs/designs/agent/compass-agent-peer-dm/design.md index 96a01668b..afd92ea33 100644 --- a/docs/designs/agent/compass-agent-peer-dm/design.md +++ b/docs/designs/agent/compass-agent-peer-dm/design.md @@ -53,7 +53,7 @@ owner-pull. No new delivery rail, no new authz model. Matt: ids are a leak; client UIs and agents address channels and topics only by their actual name. The precedent is the merged handle-addressing cutover -(`docs/designs/product/compass-handle-addressing-cutover/design.md`, PR #698, +(`docs/designs/server/compass-handle-addressing-cutover/design.md`, PR #698, RIG-2880), whose pattern this record mirrors exactly: - **Request-INPUT** fields carry the name, resolved to an id at the service diff --git a/docs/designs/agent/compass-agent-primary-lifecycle/design.md b/docs/designs/agent/compass-agent-primary-lifecycle/design.md index 19c1b1eeb..9f6928ffd 100644 --- a/docs/designs/agent/compass-agent-primary-lifecycle/design.md +++ b/docs/designs/agent/compass-agent-primary-lifecycle/design.md @@ -4,7 +4,7 @@ Status: Active Tracker: SEA > **Amends frozen contract (#1018, DL-091 / DL-032).** This record is a -> sibling amendment to `docs/designs/product/compass-issue-model/design.md` +> sibling amendment to `docs/designs/server/compass-issue-model/design.md` > (merged in #1018) and composes with the DL-070 server projection it froze. > It ratifies Matt's 2026-08-04 ruling that the board lifecycle is > **agent-primary**: "i'm not sure we should even have the UI move cards? i diff --git a/docs/designs/agent/compass-agent-spawn-despawn/design.md b/docs/designs/agent/compass-agent-spawn-despawn/design.md index 0f758665f..d6ca9604d 100644 --- a/docs/designs/agent/compass-agent-spawn-despawn/design.md +++ b/docs/designs/agent/compass-agent-spawn-despawn/design.md @@ -11,7 +11,7 @@ seams: the agent→Server lifecycle relay (spawn + despawn), the missing agent-side tool pair. Companion records this composes with: [`compass-agent-comms-tools/design.md`](../compass-agent-comms-tools/design.md) (the agent-tool + relay pattern this mirrors, DL-028/DL-029), -[`compass-server-ownership-layer/design.md`](../../product/compass-server-ownership-layer/design.md) +[`compass-server-ownership-layer/design.md`](../../server/compass-server-ownership-layer/design.md) (#995 — the Server as ownership layer; its DL-049 sibling-call-family precedent), and [`compass-agent-container-runtime.md`](../compass-agent-container-runtime.md) (the provisioning substrate the spawn drives). @@ -193,7 +193,7 @@ exist inside it is the agent's business, so a spawned peer clones whatever it has credentials for, after launch. This mirrors the same removal on `ProvisionAgentWorkspaceRequest` (server-side auto-clone is deleted, not made optional) — see the ownership record's OQ-4, superseded to *removed* -([`compass-server-ownership-layer/design.md`](../../product/compass-server-ownership-layer/design.md), +([`compass-server-ownership-layer/design.md`](../../server/compass-server-ownership-layer/design.md), OQ-4). Post-MVP, an agent tool may clone on the agent's behalf so agents need hold no git creds; until then the container's scoped credential stands. diff --git a/docs/designs/infra/runtime/compass-runnerhub-send-queue/design.md b/docs/designs/infra/runtime/compass-runnerhub-send-queue/design.md index e7bbf5e5a..a53f2a696 100644 --- a/docs/designs/infra/runtime/compass-runnerhub-send-queue/design.md +++ b/docs/designs/infra/runtime/compass-runnerhub-send-queue/design.md @@ -4,7 +4,7 @@ Status: Active Tracker: RIG-1611 — PR #75 review LOW #3, parked at review time ("no change for MVP, multi-Runner only"). Provenance: the parent product record -`docs/designs/product/compass-notification-delivery/design.md` (RIG-1569 T3) +`docs/designs/server/compass-notification-delivery/design.md` (RIG-1569 T3) ships the fan-out consumer whose head-of-line blocking motivates this record; that record is frozen (`Status: Active`) and is cited, never edited. This is the **SERVER-side counterpart** to the already-**MERGED** RUNNER-side work diff --git a/docs/designs/product/compass-attribution-simplification/design.md b/docs/designs/server/compass-attribution-simplification/design.md similarity index 98% rename from docs/designs/product/compass-attribution-simplification/design.md rename to docs/designs/server/compass-attribution-simplification/design.md index 9d6d7250a..85d7af26b 100644 --- a/docs/designs/product/compass-attribution-simplification/design.md +++ b/docs/designs/server/compass-attribution-simplification/design.md @@ -4,7 +4,7 @@ Status: Active Tracker: SEA > **Amends frozen contract (#1018, DL-068).** This record is a sibling -> amendment to `docs/designs/product/compass-issue-model/design.md` (merged in +> amendment to `docs/designs/server/compass-issue-model/design.md` (merged in > #1018): the merged record is frozen, so a later change ADDS a record. It > ratifies Matt's 2026-08-01 ruling that agent attribution is a plain display > fact — not a trust claim — reversing DL-068's hedge-unless-verified board @@ -135,7 +135,7 @@ Author this record; add DL-094 (the display-fact contract); flip DL-068 to supersede is warranted (see §Ledger delta for the DL-092 judgment). `Interfaces:` consumes the frozen records cited above; produces -`docs/designs/product/compass-attribution-simplification/design.md` and the +`docs/designs/server/compass-attribution-simplification/design.md` and the `DECISIONS.md` delta (DL-094 + the DL-068 flip). Gate: `design-ledger-gate`. ### T2 — proto: slim `AgentAttribution` (compass repo; NOT this PR) diff --git a/docs/designs/product/compass-forge-agent-notification/design.md b/docs/designs/server/compass-forge-agent-notification/design.md similarity index 100% rename from docs/designs/product/compass-forge-agent-notification/design.md rename to docs/designs/server/compass-forge-agent-notification/design.md diff --git a/docs/designs/product/compass-forge-app-credentials/design.md b/docs/designs/server/compass-forge-app-credentials/design.md similarity index 99% rename from docs/designs/product/compass-forge-app-credentials/design.md rename to docs/designs/server/compass-forge-app-credentials/design.md index 46f981970..bb26faf81 100644 --- a/docs/designs/product/compass-forge-app-credentials/design.md +++ b/docs/designs/server/compass-forge-app-credentials/design.md @@ -41,7 +41,7 @@ Six threads: app-actor is a delegation endpoint that cannot be *assigned* work (responder record: "a delegation is routed to the right stable Compass Manager … the Linear session is a doorway, not a home", - `docs/designs/product/compass-linear-agent-responder/design.md:42-45`), so + `docs/designs/server/compass-linear-agent-responder/design.md:42-45`), so a live end-to-end delegation needs a user identity to perform the human→app handoff. diff --git a/docs/designs/product/compass-forge-board-webhook-ingestion/design.md b/docs/designs/server/compass-forge-board-webhook-ingestion/design.md similarity index 100% rename from docs/designs/product/compass-forge-board-webhook-ingestion/design.md rename to docs/designs/server/compass-forge-board-webhook-ingestion/design.md diff --git a/docs/designs/product/compass-forge-integration-testing/design.md b/docs/designs/server/compass-forge-integration-testing/design.md similarity index 100% rename from docs/designs/product/compass-forge-integration-testing/design.md rename to docs/designs/server/compass-forge-integration-testing/design.md diff --git a/docs/designs/product/compass-forge-poll-driver/design.md b/docs/designs/server/compass-forge-poll-driver/design.md similarity index 100% rename from docs/designs/product/compass-forge-poll-driver/design.md rename to docs/designs/server/compass-forge-poll-driver/design.md diff --git a/docs/designs/product/compass-forge-write-path/design.md b/docs/designs/server/compass-forge-write-path/design.md similarity index 100% rename from docs/designs/product/compass-forge-write-path/design.md rename to docs/designs/server/compass-forge-write-path/design.md diff --git a/docs/designs/product/compass-handle-addressing-cutover/design.md b/docs/designs/server/compass-handle-addressing-cutover/design.md similarity index 100% rename from docs/designs/product/compass-handle-addressing-cutover/design.md rename to docs/designs/server/compass-handle-addressing-cutover/design.md diff --git a/docs/designs/product/compass-issue-model/design.md b/docs/designs/server/compass-issue-model/design.md similarity index 100% rename from docs/designs/product/compass-issue-model/design.md rename to docs/designs/server/compass-issue-model/design.md diff --git a/docs/designs/product/compass-linear-agent-responder/design.md b/docs/designs/server/compass-linear-agent-responder/design.md similarity index 99% rename from docs/designs/product/compass-linear-agent-responder/design.md rename to docs/designs/server/compass-linear-agent-responder/design.md index 5677f15cf..6aa4e31ec 100644 --- a/docs/designs/product/compass-linear-agent-responder/design.md +++ b/docs/designs/server/compass-linear-agent-responder/design.md @@ -976,9 +976,9 @@ Proposed rows: | ID | Decision | Status | Record | | --- | --- | --- | --- | -| DL-254 | The Linear Agent Session responder is a plain `POST /webhooks` `http.Handler` mounted on the compass-server network TLS door (inside `buildNetworkServer`, beside the Connect mounts, inheriting the G112/RIG-1298 guards; NOT a Connect service, NOT a dedicated ingress), fail-closed on the raw-body HMAC-SHA256 `Linear-Signature` check (bad/missing signature → 400; a stale-but-validly-signed `webhookTimestamp` is 200-with-drop, never a retry-burning 400), acking 200 before any work (Linear's 5s SLA) with all agent work async; the public base URL (webhook host + deep-link base) is a per-deployment config value, never hardcoded | Active (Matt, YYYY-MM-DD) | [linear agent responder §Part 1](product/compass-linear-agent-responder/design.md#part-1--the-webhook-receiver-on-the-network-door) | -| DL-255 | A delegated Linear session is routed to a stable Compass Manager keyed on Compass's recorded forge ownership index (`forge_authored_artifacts`, DL-055/DL-205) — NEVER a header parsed from forge text (DL-050/DL-094 forbid it reaching a routing decision); an issue with no recorded ownership row routes to the supervisor/top-level Manager via a dedicated routing channel, which decides the lane and stamps it through the DL-050 write chokepoint so later events resolve directly | Active (Matt, YYYY-MM-DD) | [linear agent responder §Part 2](product/compass-linear-agent-responder/design.md#part-2--routing-a-delegated-linear-session-to-a-stable-manager) | -| DL-256 | The Linear return path is a dumb link (Option B, Matt 2026-08-25): on `created` the responder emits one `thought` plus an `externalUrls` "Open in Compass" deep link to the resolved Manager's home channel and nothing else — NO activity relay, NO settle observation, NO Linear session-lifecycle machine. One Linear session is NOT forced 1-1 to a comms topic; the prompt lands in an issue-named topic (persisted in a new `linear_agent_sessions` table) delivered as `@linear`-authored deliver-rail messages deduped by `PostAsAccount`'s `client_request_id` on the `Linear-Delivery` UUID, but the deep link targets the home channel. Option C (coarse one-way status) is the ratified follow-up; Option A (full bidirectional relay) is off-table until Linear's activity vocabulary is configurable | Active (Matt, YYYY-MM-DD) | [linear agent responder §Part 3](product/compass-linear-agent-responder/design.md#part-3--the-return-path-a-dumb-link-option-b) | +| DL-254 | The Linear Agent Session responder is a plain `POST /webhooks` `http.Handler` mounted on the compass-server network TLS door (inside `buildNetworkServer`, beside the Connect mounts, inheriting the G112/RIG-1298 guards; NOT a Connect service, NOT a dedicated ingress), fail-closed on the raw-body HMAC-SHA256 `Linear-Signature` check (bad/missing signature → 400; a stale-but-validly-signed `webhookTimestamp` is 200-with-drop, never a retry-burning 400), acking 200 before any work (Linear's 5s SLA) with all agent work async; the public base URL (webhook host + deep-link base) is a per-deployment config value, never hardcoded | Active (Matt, YYYY-MM-DD) | [linear agent responder §Part 1](server/compass-linear-agent-responder/design.md#part-1--the-webhook-receiver-on-the-network-door) | +| DL-255 | A delegated Linear session is routed to a stable Compass Manager keyed on Compass's recorded forge ownership index (`forge_authored_artifacts`, DL-055/DL-205) — NEVER a header parsed from forge text (DL-050/DL-094 forbid it reaching a routing decision); an issue with no recorded ownership row routes to the supervisor/top-level Manager via a dedicated routing channel, which decides the lane and stamps it through the DL-050 write chokepoint so later events resolve directly | Active (Matt, YYYY-MM-DD) | [linear agent responder §Part 2](server/compass-linear-agent-responder/design.md#part-2--routing-a-delegated-linear-session-to-a-stable-manager) | +| DL-256 | The Linear return path is a dumb link (Option B, Matt 2026-08-25): on `created` the responder emits one `thought` plus an `externalUrls` "Open in Compass" deep link to the resolved Manager's home channel and nothing else — NO activity relay, NO settle observation, NO Linear session-lifecycle machine. One Linear session is NOT forced 1-1 to a comms topic; the prompt lands in an issue-named topic (persisted in a new `linear_agent_sessions` table) delivered as `@linear`-authored deliver-rail messages deduped by `PostAsAccount`'s `client_request_id` on the `Linear-Delivery` UUID, but the deep link targets the home channel. Option C (coarse one-way status) is the ratified follow-up; Option A (full bidirectional relay) is off-table until Linear's activity vocabulary is configurable | Active (Matt, YYYY-MM-DD) | [linear agent responder §Part 3](server/compass-linear-agent-responder/design.md#part-3--the-return-path-a-dumb-link-option-b) | (The `YYYY-MM-DD` and attribution cells are the driver's to stamp at freeze; the `ROW_ACTIVE_RE` grammar is `Active (, YYYY-MM-DD)`, diff --git a/docs/designs/product/compass-linear-return-path-indirection-amendment/design.md b/docs/designs/server/compass-linear-return-path-indirection-amendment/design.md similarity index 99% rename from docs/designs/product/compass-linear-return-path-indirection-amendment/design.md rename to docs/designs/server/compass-linear-return-path-indirection-amendment/design.md index f5c34b14a..09912bbd1 100644 --- a/docs/designs/product/compass-linear-return-path-indirection-amendment/design.md +++ b/docs/designs/server/compass-linear-return-path-indirection-amendment/design.md @@ -4,7 +4,7 @@ Status: Active Tracker: RIG-2794 > **Extends #625 (frozen).** This record is a sibling amendment to -> `docs/designs/product/compass-linear-agent-responder/design.md` (merged in +> `docs/designs/server/compass-linear-agent-responder/design.md` (merged in > #625). The merged record is frozen; per house convention a later change ADDS > a record. This amendment changes ONE thing in that record's Part 3 return > path: WHERE the session's `externalUrls` link points — a stable diff --git a/docs/designs/product/compass-linear-webhook-mount-amendment/design.md b/docs/designs/server/compass-linear-webhook-mount-amendment/design.md similarity index 99% rename from docs/designs/product/compass-linear-webhook-mount-amendment/design.md rename to docs/designs/server/compass-linear-webhook-mount-amendment/design.md index 5caad94cd..3af6445d0 100644 --- a/docs/designs/product/compass-linear-webhook-mount-amendment/design.md +++ b/docs/designs/server/compass-linear-webhook-mount-amendment/design.md @@ -4,7 +4,7 @@ Status: Active Tracker: RIG-2717 > **Extends #625 (frozen).** This record is a sibling amendment to -> `docs/designs/product/compass-linear-agent-responder/design.md` (merged in +> `docs/designs/server/compass-linear-agent-responder/design.md` (merged in > #625). The merged record is frozen; per house convention a later change ADDS > a record. This amendment changes two things about the frozen record's Part 1 / > T7 webhook receiver: (1) the mount **path** moves from bare `/webhooks` to diff --git a/docs/designs/product/compass-mention-offline-redelivery-pre-settle-closure.md b/docs/designs/server/compass-mention-offline-redelivery-pre-settle-closure.md similarity index 100% rename from docs/designs/product/compass-mention-offline-redelivery-pre-settle-closure.md rename to docs/designs/server/compass-mention-offline-redelivery-pre-settle-closure.md diff --git a/docs/designs/product/compass-mention-offline-redelivery.md b/docs/designs/server/compass-mention-offline-redelivery.md similarity index 100% rename from docs/designs/product/compass-mention-offline-redelivery.md rename to docs/designs/server/compass-mention-offline-redelivery.md diff --git a/docs/designs/product/compass-notification-delivery/design.md b/docs/designs/server/compass-notification-delivery/design.md similarity index 100% rename from docs/designs/product/compass-notification-delivery/design.md rename to docs/designs/server/compass-notification-delivery/design.md diff --git a/docs/designs/product/compass-server-ownership-layer-amendment/design.md b/docs/designs/server/compass-server-ownership-layer-amendment/design.md similarity index 99% rename from docs/designs/product/compass-server-ownership-layer-amendment/design.md rename to docs/designs/server/compass-server-ownership-layer-amendment/design.md index c076c39f1..d8f42ebdf 100644 --- a/docs/designs/product/compass-server-ownership-layer-amendment/design.md +++ b/docs/designs/server/compass-server-ownership-layer-amendment/design.md @@ -4,11 +4,11 @@ Status: Active Tracker: SEA > **Extends #995 (frozen).** This record is a sibling amendment to -> `docs/designs/product/compass-server-ownership-layer/design.md` (merged in +> `docs/designs/server/compass-server-ownership-layer/design.md` (merged in > #995). The merged record is frozen; per sealed convention a later change ADDS > a record. This amendment reconciles #995's forge-shaped proto type family to > the single canonical `compass.v1` family frozen by -> `docs/designs/product/compass-issue-model/design.md` (#1018). +> `docs/designs/server/compass-issue-model/design.md` (#1018). ## Problem / Intent diff --git a/docs/designs/product/compass-server-ownership-layer/design.md b/docs/designs/server/compass-server-ownership-layer/design.md similarity index 100% rename from docs/designs/product/compass-server-ownership-layer/design.md rename to docs/designs/server/compass-server-ownership-layer/design.md diff --git a/docs/designs/product/compass-zulip-threading-model/design.md b/docs/designs/server/compass-zulip-threading-model/design.md similarity index 99% rename from docs/designs/product/compass-zulip-threading-model/design.md rename to docs/designs/server/compass-zulip-threading-model/design.md index 27fb33d4d..3c999adf8 100644 --- a/docs/designs/product/compass-zulip-threading-model/design.md +++ b/docs/designs/server/compass-zulip-threading-model/design.md @@ -14,8 +14,8 @@ Status: Active > (store + comms), compass-agent (tools), compass-ui, compass-runner (gateway). > **Supersedes by citation** (never rewrites the merged records): > `compass-0.8-threading-and-session-renderer` (retired, RIG-2453), -> [compass-threading-ui](../compass-threading-ui/design.md), -> [compass-slack-thread-rendering](../compass-slack-thread-rendering.md), +> [compass-threading-ui](../../product/compass-threading-ui/design.md), +> [compass-slack-thread-rendering](../../product/compass-slack-thread-rendering.md), > ledger rows **DL-040** and **DL-041**, and **RIG-1364 T3's conversation > write-through** (the streamed-turn → comms auto-post path — D7/T7); > **clarifies** DL-037, DL-029, DL-028, DL-072. The ledger delta ships in this diff --git a/go/internal/forge/golden_capture_test.go b/go/internal/forge/golden_capture_test.go index 2522c2255..a5524dda8 100644 --- a/go/internal/forge/golden_capture_test.go +++ b/go/internal/forge/golden_capture_test.go @@ -2,7 +2,7 @@ package forge // Wire canonicalization + fixture derivation for the -update live-capture path // (leg 2 of the forge integration-testing record, -// docs/designs/product/compass-forge-integration-testing/design.md §T2; RIG-2229). +// docs/designs/server/compass-forge-integration-testing/design.md §T2; RIG-2229). // // This file is UNTAGGED on purpose: the canonicalization logic and its // invariants run in the normal credential-free `go test ./internal/forge/` diff --git a/go/internal/forge/golden_test.go b/go/internal/forge/golden_test.go index 6ab8d7f4b..be370a48c 100644 --- a/go/internal/forge/golden_test.go +++ b/go/internal/forge/golden_test.go @@ -1,7 +1,7 @@ package forge // Golden-fixture replay (leg 1 of the forge integration-testing record, -// docs/designs/product/compass-forge-integration-testing/design.md §T1). A +// docs/designs/server/compass-forge-integration-testing/design.md §T1). A // plain, untagged test that replays committed request/response fixtures from // testdata// through the existing scriptedRoundTripper stub against // the REAL forge clients, asserting BOTH halves of each exchange: the request diff --git a/go/internal/forge/livegithub_test.go b/go/internal/forge/livegithub_test.go index 049c75959..b51b4f6c4 100644 --- a/go/internal/forge/livegithub_test.go +++ b/go/internal/forge/livegithub_test.go @@ -3,7 +3,7 @@ package forge // Live-credentials oracle suite (leg 2 of the forge integration-testing record, -// docs/designs/product/compass-forge-integration-testing/design.md §T2). Guarded +// docs/designs/server/compass-forge-integration-testing/design.md §T2). Guarded // by //go:build livegithub so a bare `go test ./internal/forge/` never compiles // it — the untagged golden battery (golden_test.go) stays credential-free. // diff --git a/go/internal/gen/compass/v1/forge.pb.go b/go/internal/gen/compass/v1/forge.pb.go index c1f7ff9dd..7525322c6 100644 --- a/go/internal/gen/compass/v1/forge.pb.go +++ b/go/internal/gen/compass/v1/forge.pb.go @@ -1,5 +1,5 @@ // The compass.v1 forge notification + shared carrier leaf (RIG-1731 A1, design -// docs/designs/product/compass-server-ownership-layer-amendment/design.md §A1). +// docs/designs/server/compass-server-ownership-layer-amendment/design.md §A1). // // INTERNAL-ONLY surface. Like agent.proto / agent_gateway.proto / runner.proto, // and unlike compass.proto / comms.proto (the public generated-client door diff --git a/go/internal/linearagent/routing.go b/go/internal/linearagent/routing.go index 9a184c446..a8c644911 100644 --- a/go/internal/linearagent/routing.go +++ b/go/internal/linearagent/routing.go @@ -1,7 +1,7 @@ package linearagent // Responder routing resolution (RIG-2717 T4, design -// docs/designs/product/compass-linear-agent-responder/design.md §Part 2 / §T4). +// docs/designs/server/compass-linear-agent-responder/design.md §Part 2 / §T4). // // A Linear delegation or @mention names the Compass app, never a specific // Manager, so the bridge must resolve which stable Manager runs the session. diff --git a/go/internal/store/forge_authored.go b/go/internal/store/forge_authored.go index cb8fb231c..7c3e63cd8 100644 --- a/go/internal/store/forge_authored.go +++ b/go/internal/store/forge_authored.go @@ -10,7 +10,7 @@ import ( ) // The DL-055 forge ownership index (design -// docs/designs/product/compass-forge-write-path/design.md §T7): the durable +// docs/designs/server/compass-forge-write-path/design.md §T7): the durable // record of every forge artifact Compass AUTHORED on behalf of an agent. The // write chokepoint (T4) records the row AND the F3 idempotency memo in one // statement on a create success; a provider error records nothing. The dedup diff --git a/go/internal/store/forge_authored_pgtest_test.go b/go/internal/store/forge_authored_pgtest_test.go index 2b9f01c7d..afa7c82f4 100644 --- a/go/internal/store/forge_authored_pgtest_test.go +++ b/go/internal/store/forge_authored_pgtest_test.go @@ -3,7 +3,7 @@ package store // DL-055 forge ownership-index store contracts (design -// docs/designs/product/compass-forge-write-path/design.md §T7 test cycle, the +// docs/designs/server/compass-forge-write-path/design.md §T7 test cycle, the // DL-174 pair: this pgtest suite plus the in-memory reference in // forge_authored_test.go): the migration 0002 table shape, the idempotent // coordinate upsert, the FK RESTRICT on agent/owner, the by-agent scan order, diff --git a/go/internal/store/forge_authored_test.go b/go/internal/store/forge_authored_test.go index c69e45dd3..772adee80 100644 --- a/go/internal/store/forge_authored_test.go +++ b/go/internal/store/forge_authored_test.go @@ -1,7 +1,7 @@ package store // The hermetic default-gate half of the DL-174 pair for the DL-055 ownership -// index (design docs/designs/product/compass-forge-write-path/design.md §T7): +// index (design docs/designs/server/compass-forge-write-path/design.md §T7): // the pure-Go contract that needs no Postgres — the pre-DB argument guards, the // empty-clientRequestID always-miss short-circuit, and the NULL client_request_id // mapping. The real-Postgres row contracts live in the pgtest sibling diff --git a/go/internal/store/forge_subscriptions.go b/go/internal/store/forge_subscriptions.go index 650bdb13d..10cf297e4 100644 --- a/go/internal/store/forge_subscriptions.go +++ b/go/internal/store/forge_subscriptions.go @@ -12,7 +12,7 @@ import ( ) // The DL-053 agent-notification subscription writer (RIG-2732 Piece 1, design -// docs/designs/product/compass-notification-delivery/design.md): the Server-side +// docs/designs/server/compass-notification-delivery/design.md): the Server-side // Postgres row that records an agent's standing interest in one forge artifact. // agent_forge_subscriptions is the per-subscriber DELIVERY-cursor table // (delivered_revision/delivered_at); forge_artifact_cursors is the shared diff --git a/go/server/forge_e2e_pgtest_test.go b/go/server/forge_e2e_pgtest_test.go index ed61f8143..e4371af66 100644 --- a/go/server/forge_e2e_pgtest_test.go +++ b/go/server/forge_e2e_pgtest_test.go @@ -3,7 +3,7 @@ package server // End-to-end T8 of the FROZEN Compass forge-write design -// (docs/designs/product/compass-forge-write-path/design.md §T8, acceptance +// (docs/designs/server/compass-forge-write-path/design.md §T8, acceptance // :876-885): the WHOLE agent-initiated forge-WRITE wire, driven over a REAL // per-container AgentGateway unix socket against a real Postgres + a real // Runner-over-stub-engine, with the forge chokepoint mounted on the diff --git a/go/server/serve.go b/go/server/serve.go index 6dd962a69..bc0312eda 100644 --- a/go/server/serve.go +++ b/go/server/serve.go @@ -161,7 +161,7 @@ type ForgeConfig struct { // ForgeAppConfig is the GitHub App credential the board webhook-ingestion lane // runs on (RIG-2883, frozen surface at -// docs/designs/product/compass-forge-agent-notification/design.md:1035-1048). +// docs/designs/server/compass-forge-agent-notification/design.md:1035-1048). // The lane runs iff AppID != 0 AND both AppPrivateKeySecret and // AppWebhookSecretName are declared (mirrors validateForgeSecret's fail-fast); // no App -> board ingestion hard-off with a boot Warn (Constraint #3). diff --git a/packages/compass-agent/src/gen/compass/v1/forge_pb.ts b/packages/compass-agent/src/gen/compass/v1/forge_pb.ts index fddac2f3d..c4f5794e9 100644 --- a/packages/compass-agent/src/gen/compass/v1/forge_pb.ts +++ b/packages/compass-agent/src/gen/compass/v1/forge_pb.ts @@ -1,5 +1,5 @@ // The compass.v1 forge notification + shared carrier leaf (RIG-1731 A1, design -// docs/designs/product/compass-server-ownership-layer-amendment/design.md §A1). +// docs/designs/server/compass-server-ownership-layer-amendment/design.md §A1). // // INTERNAL-ONLY surface. Like agent.proto / agent_gateway.proto / runner.proto, // and unlike compass.proto / comms.proto (the public generated-client door diff --git a/proto/compass/v1/forge.proto b/proto/compass/v1/forge.proto index b75908b6f..889b6c22c 100644 --- a/proto/compass/v1/forge.proto +++ b/proto/compass/v1/forge.proto @@ -1,5 +1,5 @@ // The compass.v1 forge notification + shared carrier leaf (RIG-1731 A1, design -// docs/designs/product/compass-server-ownership-layer-amendment/design.md §A1). +// docs/designs/server/compass-server-ownership-layer-amendment/design.md §A1). // // INTERNAL-ONLY surface. Like agent.proto / agent_gateway.proto / runner.proto, // and unlike compass.proto / comms.proto (the public generated-client door