Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/designs/DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ check enforces the mechanical half. Full rationale:
| DL-324 | The forge live-test Linear credential is app-actor ONLY — DL-308's retain-one-Linear-user-credential carve-out is dropped (Matt's RIG-3096 pivot): the `livegithub` oracle's Linear legs gate on a single env var, `LINEAR_FORGE` (`go/internal/forge/livegithub_test.go:65`, consumed by `requireLinear` :91-99), minted as an app-actor token per CI run by `tools/forge-linear-token/index.ts` via the client_credentials grant from the `LINEAR_FORGE_CLIENT_ID`/`_SECRET` Actions secrets (`.github/workflows/ci.yml:1002-1004`) — with NO `LINEAR_FORGE_USER_TOKEN` (zero code hits) and no human→app delegation-setup step in CI. Supersedes DL-308's Linear-user-cred clause ONLY — DL-308's GitHub App-installation-token-mint clause stays live, so DL-308 itself remains Active | Active (Matt, 2026-09-02) | [forge app credentials](server/compass-forge-app-credentials/design.md) |
| DL-342 | The forge state-transition write op is a parallel-arm pair (`TransitionIssueState` / `TransitionPullRequestState` on `forge.Provider`, `transition_issue_state` / `transition_pull_request_state` on `ForgeCallRequest`) over a portable `{open, closed}` core plus per-provider refinements carried in one `TransitionState` input (`close_reason` for GitHub issues, `workflow_state` for Linear). Every mismatch fails LOUD and in-band as `invalid_argument` before any provider call: a refinement the target provider cannot express, an unknown or ambiguous Linear state name, a named state whose type contradicts the portable target, and — per the OQ-2 ruling — a default resolution finding MORE THAN ONE candidate state, which names the candidates and demands an explicit `workflow_state` rather than taking a positional guess. Defaulting happens only where the target is unambiguous (default-close resolves against `completed`, never `canceled`). The Linear PR half returns `ErrUnsupported` (Linear is issues-only). Transitions are NOT F3-deduped and NOT DL-055-recorded — not the comment arm's reason (a comment has no representable coordinate) but because a transition targets a coordinate whose row is a write-once authorship fact whose `client_request_id` backs the create's F3 memo, so routing a transition through the record path would overwrite it. Amends DL-241's tool count by citation (twelve tools, rule unchanged). | Active (Matt, 2026-09-07) | [forge state transition](server/compass-forge-state-transition/design.md) |
| DL-343 | Forge transition actor attribution rides a CONSUMABLE `forge_state_transitions` memo, never a parsed-text or author-row proxy: written after a successful transition at the same attribution chokepoint every forge write rides, then consumed on match at the notify lane so the provider-echoed STATE event resolves to the acting agent. Tenant-scoped like its `forge_authored_artifacts` sibling, and fail-open on a miss (a late webhook loses attribution; it never blocks or misattributes). This is the contract surface the RIG-3326 self-origin suppression record's STATE arm keys on, and it settles that cross-record question in the memo's favour: RIG-3326's frozen text describes the actor as stamped onto the emitted event, so its STATE arm bends to a memo lookup at the actor-resolution seam. The rejected alternative was a synthetic STATE event emitted at the chokepoint — equivalent for the suppression outcome, but it invents an event the forge never sent. Landing the seam is what closes RIG-3326's interim-open STATE arm. | Active (Matt, 2026-09-07) | [forge state transition](server/compass-forge-state-transition/design.md) |
| DL-344 | Channels fold into the agent tree as an additive `Channel.parent_agent_id` attach edge (field 11) plus a `ReparentChannel` RPC, and membership becomes a SECOND AXIS from kind/placement: a new `ChannelMembershipMode` (field 12, column `channels.membership_mode`) with `EXPLICIT = 0` keeping today's stored `channel_members` behaviour and `TREE = 1` deriving the member set from the anchoring agent's subtree PLUS that agent's `owner_user_id` (the owner term is load-bearing: it is the second disjunct of the participant probe and the reason a human owner can post at all), with NO managed member list (Matt's RIG-1622 ruling: tree-only membership is the right default for tree-hung channels, but individually-managed members must stay possible). Rejected: implying the mode from `parent_agent_id IS NOT NULL` (forecloses a hand-membered channel that still lives in the tree) and a third `ChannelKind` value (kind is the DM-vs-channel axis; the retired `CHANNEL_KIND_GROUP_DM = 2` is the cautionary tale). Consequence: a new `ChannelParticipant` probe with a recursive actor-to-root ancestor arm is added BESIDE the surviving `ChannelMemberExists` (which two callers still need for stored-member-row semantics) and `requireChannelMember` rebinds onto it, so the `AppendMessage` post gate GAINS a derived arm rather than staying unchanged; the five `FROM channel_members` delivery queries need their DRIVING relation unioned with the derived set, not a new WHERE disjunct; `channel_members.subscribed` has no home on a row-less channel, so TREE subscription moves to an override-only `channel_subscriptions` table. `ChannelGroup` is NOT deleted — it survives as machinery for the reserved `__dm__`/`__coordination__` namespaces and as the home of SHARED spaces, but stops being the sidebar organizer | Active (Matt, 2026-09-07) | [channels in the agent tree §3](ui/compass-channels-in-agent-tree/design.md#3-two-membership-modes-posting-follows-the-tree) |
| DL-345 | An agent-attached channel's read grant is the OWNER set — the anchoring agent's `owner_user_id` and that user's agents — as a deliberate product decision (Matt: "by default, yes, all agents under an owner can read all channels from that owner"), NOT as a reuse of existing OWNER-group semantics: the owner-set `viewer` CTE exists only in the two GROUP queries (`go/internal/store/queries/channels.sql:93`, `:118`), while all three CHANNEL predicates grant non-member access solely through the SHARED-group arm (`:152-153`, `:181-182`, `:211-212`), so the disjunct mints a new visibility class rather than matching a precedent. Attachment bounds only the NON-MEMBER grant — members read regardless of owner, since cross-owner members exist (a converted DM carries both owners). A per-channel ACL is the named successor that will gate specific channels off, slotting in as one more conjunct in the three predicate copies plus the participant probe; this design keeps that insertion cheap and does not attempt the ACL | Active (Matt, 2026-09-07) | [channels in the agent tree §2](ui/compass-channels-in-agent-tree/design.md#2-the-visibility-invariant) |

## Agent roles & prompts

Expand Down
Loading
Loading