Skip to content

Commit bea1fa3

Browse files
authored
feat(slack-gateway): add workspace install management (#223)
* fix(slack): route install target lookup to backend fastapi * docs: add channel install ownership model * feat(slack-gateway): add workspace management
1 parent 9764daa commit bea1fa3

10 files changed

Lines changed: 1351 additions & 24 deletions
Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
---
2+
date: 2026-04-17
3+
author: Onur Solmaz <onur@textcortex.com>
4+
title: Channel Install Ownership and Management Architecture
5+
tags: [spritz, channel-gateway, install, ownership, management, architecture]
6+
---
7+
8+
## Overview
9+
10+
This document defines who owns a shared channel installation after install
11+
target selection, who can manage that installation later, and how reinstall,
12+
target changes, reconnects, disconnects, and later install configuration
13+
changes should behave.
14+
15+
It builds on the install-target selection model and keeps Spritz generic:
16+
17+
- Spritz owns the browser UX and generic install-management surfaces
18+
- the deployment owns target eligibility, target semantics, and authorization
19+
- the durable installation remains keyed by route identity, not by browser
20+
session or installer identity alone
21+
22+
Related docs:
23+
24+
- [Channel Install Target Selection Architecture](2026-04-17-channel-install-target-selection-architecture.md)
25+
- [Shared Channel Concierge Lifecycle Architecture](2026-03-31-shared-channel-concierge-lifecycle-architecture.md)
26+
- [Channel Install Result Surface](2026-04-02-channel-install-result-surface.md)
27+
- [Shared App Tenant Routing Architecture](2026-03-23-shared-app-tenant-routing-architecture.md)
28+
29+
## Problem
30+
31+
Install target selection answers which target should back a workspace, but it
32+
does not by itself answer who owns the resulting installation.
33+
34+
Those are not always the same thing:
35+
36+
- the installer may be an individual actor
37+
- the chosen target may be owned by that individual
38+
- or the chosen target may be owned by a group that the installer manages
39+
40+
If the installer is treated as the canonical owner even when the chosen target
41+
belongs to a group, the model becomes awkward:
42+
43+
- the selected target and installation ownership no longer line up
44+
- later management rights become unclear
45+
- reinstall and recovery paths risk silently re-binding to the wrong owner
46+
47+
## Core Decision
48+
49+
The effective owner of a shared channel installation must be derived from the
50+
selected target, not from the installer identity.
51+
52+
That means:
53+
54+
- an individually owned target creates or updates an individually owned
55+
installation
56+
- a group-owned target creates or updates a group-owned installation
57+
- the installer identity remains audit metadata, not the canonical owner
58+
59+
Management rights must then follow the effective owner of the installation.
60+
61+
## Pinned Decisions
62+
63+
### Route identity stays separate from ownership
64+
65+
The durable installation identity remains:
66+
67+
- `principalId`
68+
- `provider`
69+
- `externalScopeType`
70+
- `externalTenantId`
71+
72+
Ownership is attached to that installation record, but does not replace the
73+
route key.
74+
75+
This keeps the model ready for multiple shared apps in the future:
76+
77+
- one app plus one workspace is one installation
78+
- a second app for the same workspace is a different installation because it
79+
has a different `principalId`
80+
81+
Spritz and deployments must therefore avoid any global "one workspace only"
82+
assumption.
83+
84+
### Effective owner is target-derived
85+
86+
The deployment resolves the selected target into both:
87+
88+
- the opaque `presetInputs` that should be saved on the installation
89+
- the effective owner principal for that installation
90+
91+
Two generic cases matter in v1:
92+
93+
- individually owned target -> individually owned installation
94+
- group-owned target -> group-owned installation
95+
96+
Spritz does not need a built-in taxonomy for owner types beyond what the
97+
deployment already uses for authorization and display.
98+
99+
### Installer identity is audit-only
100+
101+
The actor who completed provider OAuth is still useful, but only as audit
102+
metadata:
103+
104+
- who installed or reinstalled the app
105+
- who completed a reconnect
106+
- who last changed the target
107+
108+
The installer should not override the effective installation owner.
109+
110+
### Management rights follow the effective owner
111+
112+
The caller may manage an installation only if the deployment says the caller is
113+
authorized for that installation's effective owner.
114+
115+
Spritz should not attempt to infer that policy locally.
116+
117+
For v1, Spritz should treat install management as an installation-centric UI
118+
over deployment-owned authorization:
119+
120+
- list manageable installations
121+
- show the current selected target summary
122+
- allow `change target`
123+
- allow `reconnect`
124+
- allow `disconnect`
125+
126+
The deployment remains the source of truth for:
127+
128+
- which installations the caller may see
129+
- which actions are currently allowed
130+
- whether the selected replacement target is valid
131+
132+
### Management API is installation-centric and server-driven
133+
134+
Spritz should manage connected workspaces through one generic installation
135+
surface rather than through provider-specific management pages.
136+
137+
Pinned v1 operations:
138+
139+
- `channel.installations.list`
140+
- `channel.installation.target.update`
141+
- `channel.installation.reconnect`
142+
- `channel.installation.disconnect`
143+
144+
The UI should not infer permissions or workflow state locally.
145+
146+
Instead, the deployment should return install rows with enough information to
147+
render the page safely:
148+
149+
- stable installation identifier
150+
- route summary
151+
- current state
152+
- current target summary
153+
- `allowedActions`
154+
- optional `problemCode`
155+
156+
That keeps ownership and authorization logic on the server while letting Spritz
157+
remain generic.
158+
159+
### Reinstall updates in place only for the same effective owner
160+
161+
Provider-driven reinstall should reuse the same `(principalId, provider,
162+
externalScopeType, externalTenantId)` installation when possible.
163+
164+
Reinstall may update provider-side auth and other mutable metadata in place,
165+
but it must not silently change ownership.
166+
167+
Pinned rule:
168+
169+
- if the reinstall resolves to the same effective owner, update in place
170+
- if it resolves to a different effective owner, return conflict
171+
172+
This prevents silent workspace takeover during normal provider reinstall flows.
173+
174+
### Explicit target change may change ownership
175+
176+
A deliberate management action to change the selected target may also change
177+
the effective owner.
178+
179+
That is allowed, but only as an explicit management operation, not as an
180+
incidental side effect of reinstall.
181+
182+
Pinned rule:
183+
184+
- the caller must be authorized to manage the current installation
185+
- the deployment must validate the newly selected target
186+
- the target change and owner change must commit atomically
187+
188+
Examples:
189+
190+
- individual-owned install -> group-owned target => installation becomes
191+
group-owned
192+
- group-owned install -> individually owned target => installation becomes
193+
individually owned
194+
195+
### Invalid or inaccessible target fails closed
196+
197+
If the saved target is deleted, becomes inaccessible, or otherwise no longer
198+
resolves cleanly, Spritz must not silently fall back to another target.
199+
200+
Pinned rule:
201+
202+
- route resolution fails closed until the installation is repaired
203+
- the install-management surface must show that operator action is required
204+
- an authorized manager must select a replacement target explicitly
205+
206+
Spritz should not auto-retarget and should not invent fallback target
207+
selection behavior.
208+
209+
### `presetInputs` is for target selection, not long-term provider config
210+
211+
The saved opaque `presetInputs` on an installation should mean:
212+
213+
- which target this installation points at
214+
215+
It should not become the catch-all home for future mutable provider behavior.
216+
217+
Pinned split:
218+
219+
- route identity determines which shared app and external tenant this
220+
installation serves
221+
- `presetInputs` determines which deployment-owned target backs that
222+
installation
223+
- installation config determines future provider-specific mutable behavior for
224+
that installation
225+
226+
Examples of future installation config:
227+
228+
- provider-specific channel allowlists
229+
- reply policy toggles
230+
- required or optional delivery constraints
231+
- posting or mention behavior
232+
233+
Those settings may affect runtime behavior, but they are not the same concept
234+
as target selection and should evolve through a separate installation-config
235+
surface.
236+
237+
### Disconnect and uninstall are soft
238+
239+
Provider uninstall or product-side disconnect should soft-disconnect the
240+
installation, not hard-delete it immediately.
241+
242+
Pinned rule:
243+
244+
- routing stops immediately
245+
- provider auth may be cleared according to deployment policy
246+
- the durable installation record remains
247+
- the logical concierge binding may remain for later reuse
248+
249+
This keeps reconnect flows simple and consistent with the existing shared
250+
channel lifecycle model.
251+
252+
## UX Consequences For Spritz
253+
254+
Spritz should present connected workspaces as installation records, not as one
255+
global account-link concept.
256+
257+
For each manageable installation, Spritz should show at least:
258+
259+
- provider/workspace identity
260+
- current state
261+
- current selected target summary
262+
- available actions
263+
264+
The minimum action set is:
265+
266+
- change target
267+
- reconnect
268+
- disconnect
269+
270+
When an installation is in a broken but still durable state, the UI should
271+
still render the row and show a repair-needed state through `problemCode`
272+
rather than dropping the installation from the page.
273+
274+
Spritz does not need to expose deployment-specific ownership rules in the UI.
275+
It only needs to render the installations and actions that the deployment says
276+
the caller may manage.
277+
278+
## Contract Consequences
279+
280+
This model implies a few stable contract expectations, even if exact endpoint
281+
shapes vary by deployment:
282+
283+
- installations-list APIs must return server-driven action availability
284+
- target resolution must return both target selection data and effective owner
285+
- installation persistence must store the saved selection on the durable
286+
installation
287+
- install-management APIs must authorize against the effective owner, not just
288+
the original installer
289+
- reinstall APIs must detect effective-owner mismatch and return conflict
290+
- management-target-change APIs must update target and owner together
291+
- mutable installation-config APIs must stay separate from target-selection APIs
292+
293+
These behaviors matter more than the exact transport details.
294+
295+
## Validation
296+
297+
At minimum, an implementation should validate:
298+
299+
- reinstall of the same route and same effective owner updates in place
300+
- reinstall of the same route and different effective owner returns conflict
301+
- changing to a new valid target updates the installation atomically
302+
- changing to a target owned by a different principal updates effective owner
303+
- deleting or invalidating the saved target blocks routing until repair
304+
- disconnect stops routing but preserves the installation for reconnect
305+
- install rows expose the correct `allowedActions` and `problemCode`
306+
- future provider-specific configuration can change without rewriting saved
307+
target selection
308+
- the same external tenant can still have multiple installations later when
309+
`principalId` differs
310+
311+
## Follow-Ups
312+
313+
The next design work should define the generic management surfaces that Spritz
314+
needs for:
315+
316+
- listing manageable installations
317+
- updating the selected target on an installation
318+
- reconnecting a disconnected installation
319+
- surfacing repair-needed state when the saved target is no longer valid
320+
- defining the generic installation-config surface for provider-specific
321+
mutable settings

docs/2026-04-17-channel-install-target-selection-architecture.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ that selection resolves into the final concierge binding.
2525

2626
Related docs:
2727

28+
- [Channel Install Ownership and Management Architecture](2026-04-17-channel-install-ownership-and-management-architecture.md)
2829
- [Shared Channel Concierge Lifecycle Architecture](2026-03-31-shared-channel-concierge-lifecycle-architecture.md)
2930
- [Channel Install Result Surface](2026-04-02-channel-install-result-surface.md)
3031
- [Agent Profile API](2026-03-30-agent-profile-api.md)
@@ -169,6 +170,28 @@ Reasons:
169170
The chosen opaque `presetInputs` should therefore be persisted on the durable
170171
installation object that the deployment already owns, not in Spritz core state.
171172

173+
## What `presetInputs` Is Not
174+
175+
`presetInputs` is the right place for install-time target selection, but it is
176+
not the right long-term container for every mutable provider-specific setting.
177+
178+
Pinned split:
179+
180+
- `presetInputs` selects the target that backs the installation
181+
- separate installation config should hold future provider-specific mutable
182+
settings
183+
184+
Examples of installation config that should not be folded into target
185+
selection:
186+
187+
- provider-specific channel allowlists
188+
- reply-policy toggles
189+
- required or optional delivery constraints
190+
- posting or mention behavior
191+
192+
That split keeps install target selection stable even as provider-specific
193+
behavior grows over time.
194+
172195
## Why Existing `agentRef` Is Not Enough
173196

174197
The existing [Agent Profile API](2026-03-30-agent-profile-api.md) is for a

0 commit comments

Comments
 (0)