|
| 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 |
0 commit comments