Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
87db37d
docs: plan React migration for management UI
osolmaz Apr 25, 2026
d55aadc
docs: clarify chat UI settings boundary
osolmaz Apr 25, 2026
ac47925
feat: migrate Slack management pages to React
osolmaz Apr 25, 2026
d500c34
fix: harden slack management ui migration
osolmaz Apr 25, 2026
4646b4e
fix: make slack management routing configurable
osolmaz Apr 25, 2026
650471c
fix: keep channel routes mention-gated by default
osolmaz Apr 25, 2026
783bae7
fix: preserve install result errors in settings ui
osolmaz Apr 25, 2026
c0e0a32
fix: harden slack settings migration edges
osolmaz Apr 25, 2026
e558ab2
fix: keep slack install state out of urls
osolmaz Apr 25, 2026
707d91a
docs: align slack settings routes
osolmaz Apr 25, 2026
8e6f13f
fix: harden slack settings management
osolmaz Apr 25, 2026
446ec33
docs: align slack management api routes
osolmaz Apr 25, 2026
29693dd
fix: disable caching for slack management api
osolmaz Apr 25, 2026
c28678a
fix: scope pending slack installs by request
osolmaz Apr 25, 2026
32bd37f
fix: keep settings header compact on mobile
osolmaz Apr 25, 2026
41336d2
fix: send slack redirects to react host
osolmaz Apr 25, 2026
067cdea
fix: keep oauth picker on gateway origin
osolmaz Apr 25, 2026
386d6a2
fix: keep legacy slack pages cross-origin
osolmaz Apr 25, 2026
cf4b57a
fix: route slack settings through gateway
osolmaz Apr 25, 2026
90c4f36
fix: confirm slack route removal
osolmaz Apr 25, 2026
c670206
fix: harden slack settings routing
osolmaz Apr 25, 2026
032a643
fix: preserve slack settings route identity
osolmaz Apr 25, 2026
fac697f
fix: honor slack management api contracts
osolmaz Apr 25, 2026
c3c5e22
fix: preserve typed slack install failures
osolmaz Apr 25, 2026
da29c9c
fix: infer prefixed ui root basename
osolmaz Apr 25, 2026
0d29bf9
fix: show legacy channel policy rows in settings
osolmaz Apr 25, 2026
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
8 changes: 8 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ This repository is open-source and must remain provider- and organization-agnost
- Never use TextCortex hostnames in test fixtures.
- New or changed data structures, schemas, DTOs, CRDs, and API models must use `type` as the field name, never `kind`. If an external payload already sends `kind`, convert it at the boundary with an alias and keep the internal field named `type`.

## UI Ownership Rules (Mandatory)

- Add user-facing UI inside the Spritz React app under `ui/`.
- Do not add new server-rendered HTML pages in gateway, API, operator, or integration services.
- Integration and gateway services should expose redirects, callbacks, event ingestion, and JSON APIs for the React UI to consume.
- If a temporary server-rendered page is unavoidable for an external protocol callback, document why it cannot be served by the React app and keep it minimal.
- When touching existing server-rendered integration pages, prefer migrating the surface to React over extending Go templates.

Exception:

- Documentation `author` front matter may use a real maintainer identity, including a real email address, when the document is intentionally attributed to that maintainer.
Expand Down
9 changes: 7 additions & 2 deletions docs/2026-04-24-channel-install-message-policy-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,16 @@ Recommended operation:

- `channel.installation.config.update`

`installationId` is a deployment-backend-assigned opaque ID. Spritz should
receive it from the installation-management API, render it in UI URLs when
needed, and send it back unchanged. Spritz must not generate it, parse it, or
assume any specific prefix or ID-generation scheme.

Recommended request shape:

```json
{
"installationId": "inst_123",
"installationId": "opaque-installation-id",
"installationConfig": {
"channelPolicies": [
{
Expand All @@ -171,7 +176,7 @@ Recommended response shape:
```json
{
"installation": {
"id": "inst_123",
"id": "opaque-installation-id",
"provider": "slack",
"externalScopeType": "workspace",
"externalTenantId": "T1234567890",
Expand Down
75 changes: 52 additions & 23 deletions docs/2026-04-24-channel-installation-data-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ Spritz should define the generic channel-installation contract.
That includes:

- provider route identity fields
- stable installation, connection, and route IDs
- stable opaque installation, connection, and route IDs
- management API shapes
- UI routing and rendering expectations
- gateway behavior for routing and mention policy
Expand All @@ -121,6 +121,33 @@ The entity names in this document are canonical logical names. The SQL below
is an illustrative relational implementation shape, not a requirement that
Spritz core ships or runs these migrations.

## ID Ownership And Opaqueness

The deployment-owned backend assigns channel-installation IDs.

Spritz must treat these IDs as opaque strings. Spritz should list them, render
them into URLs, and send the same values back on management requests. Spritz
must not generate them, parse them, derive meaning from prefixes, or assume
that they encode database rows, Slack workspace IDs, Slack channel IDs, agent
IDs, users, organizations, or runtime instances.

The required ID properties are:

- stable for the lifetime of the logical record
- unique within the deployment-owned backend
- safe to use in URLs and logs
- non-sequential from Spritz's perspective
- not derived from external provider IDs or internal database row numbers

The Spritz contract intentionally does not require TypeID, UUID, ULID, or any
specific prefix format. A deployment may use typed IDs internally, but that is
an implementation detail of the backend that owns the storage and uniqueness
constraints.

Examples in this document use placeholder IDs such as
`opaque-installation-id`, `opaque-connection-id`, and `opaque-route-id`. They
are placeholders, not required literal formats.

## Naming Decisions

### `provider`
Expand Down Expand Up @@ -220,7 +247,7 @@ A relational implementation might use this shape:

```sql
CREATE TABLE channel_installation (
id VARCHAR(32) PRIMARY KEY,
id VARCHAR(64) PRIMARY KEY,
provider VARCHAR(32) NOT NULL,
app_principal_id VARCHAR(128) NOT NULL,
external_installation_key VARCHAR(256) NOT NULL,
Expand Down Expand Up @@ -250,7 +277,7 @@ Column justifications:

| Column | Decision | Justification |
|---|---|---|
| `id` | keep | Stable internal ID for APIs and UI. External provider IDs should not be the primary product URL. |
| `id` | keep | Stable opaque backend-assigned ID for APIs and UI. External provider IDs and database row numbers should not be the primary product URL. |
| `provider` | keep | Identifies the messaging provider, such as `slack`, `discord`, or `msteams`. Routing and provider adapters need this. |
| `app_principal_id` | keep | Identifies which shared app or gateway identity receives events for this route. This is not the product owner. The old `principal_id` name is too vague. |
| `external_installation_key` | keep | Provider-adapter-generated stable key for the external install surface. This avoids a core `scopeType` enum while still giving routing a deterministic key. |
Expand All @@ -266,7 +293,7 @@ Example Slack row:

```json
{
"id": "ci_01k...",
"id": "opaque-installation-id",
"provider": "slack",
"appPrincipalId": "shared-slack-gateway",
"externalInstallationKey": "workspace:T021GRS5F4P",
Expand Down Expand Up @@ -298,8 +325,8 @@ A relational implementation might use this shape:

```sql
CREATE TABLE channel_connection (
id VARCHAR(32) PRIMARY KEY,
installation_id VARCHAR(32) NOT NULL,
id VARCHAR(64) PRIMARY KEY,
installation_id VARCHAR(64) NOT NULL,
display_name VARCHAR(512) NULL,
is_default BOOLEAN NOT NULL DEFAULT FALSE,
status VARCHAR(32) NOT NULL,
Expand All @@ -322,7 +349,7 @@ Column justifications:

| Column | Decision | Justification |
|---|---|---|
| `id` | keep | Stable internal connection ID for APIs and UI. |
| `id` | keep | Stable opaque backend-assigned connection ID for APIs and UI. |
| `installation_id` | keep | Parent external installation. A connection cannot exist without an installed provider app. |
| `display_name` | keep | Human-facing label in settings. The UI should not need Spritz runtime details to list connections. |
| `is_default` | keep | Defines fallback routing when a channel has no explicit route. Without this, workspace-mode behavior is ambiguous. |
Expand Down Expand Up @@ -352,7 +379,7 @@ A relational implementation might use this shape:

```sql
CREATE TABLE spritz_channel_connection (
connection_id VARCHAR(32) PRIMARY KEY,
connection_id VARCHAR(64) PRIMARY KEY,
preset_id VARCHAR(128) NOT NULL,
preset_inputs JSON NULL,
spritz_binding_key VARCHAR(256) NULL,
Expand Down Expand Up @@ -408,9 +435,9 @@ A relational implementation might use this shape:

```sql
CREATE TABLE channel_route (
id VARCHAR(32) PRIMARY KEY,
installation_id VARCHAR(32) NOT NULL,
connection_id VARCHAR(32) NOT NULL,
id VARCHAR(64) PRIMARY KEY,
installation_id VARCHAR(64) NOT NULL,
connection_id VARCHAR(64) NOT NULL,
external_channel_id VARCHAR(256) NOT NULL,
require_mention BOOLEAN NOT NULL DEFAULT TRUE,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
Expand Down Expand Up @@ -438,7 +465,7 @@ Column justifications:

| Column | Decision | Justification |
|---|---|---|
| `id` | keep | Stable internal route ID for update, delete, and audit. |
| `id` | keep | Stable opaque backend-assigned route ID for update, delete, and audit. |
| `installation_id` | keep | Scopes the route to one external app installation. Provider channel IDs are not globally unique across installs. |
| `connection_id` | keep | Destination for messages from this external channel. |
| `external_channel_id` | keep | Provider channel ID from incoming events. This is the actual routing selector. |
Expand Down Expand Up @@ -585,7 +612,7 @@ Example request:

```json
{
"installationId": "ci_123",
"installationId": "opaque-installation-id",
"query": "support"
}
```
Expand Down Expand Up @@ -614,13 +641,13 @@ Examples:

```http
GET /channel/installations
GET /channel/installations/ci_123
GET /channel/installations/ci_123/connections
POST /channel/installations/ci_123/connections
PATCH /channel/connections/cc_456
GET /channel/connections/cc_456/routes
PUT /channel/connections/cc_456/routes/C0ANJGDB4Q5
DELETE /channel/routes/cr_789
GET /channel/installations/{installationId}
GET /channel/installations/{installationId}/connections
POST /channel/installations/{installationId}/connections
PATCH /channel/connections/{connectionId}
GET /channel/connections/{connectionId}/routes
PUT /channel/connections/{connectionId}/routes/C0ANJGDB4Q5
DELETE /channel/routes/{routeId}
```

The list and detail responses should be server-driven. Spritz should use
Expand All @@ -632,7 +659,7 @@ Example installation list response:
{
"installations": [
{
"id": "ci_123",
"id": "opaque-installation-id",
"provider": "slack",
"displayName": "Example Workspace",
"status": "active",
Expand Down Expand Up @@ -672,8 +699,8 @@ set for one connection:

```json
{
"installationId": "ci_123",
"connectionId": "cc_456",
"installationId": "opaque-installation-id",
"connectionId": "opaque-connection-id",
"channelPolicies": [
{
"externalChannelId": "C0ANJGDB4Q5",
Expand Down Expand Up @@ -808,6 +835,8 @@ Minimum validation for this model:

- Keep `provider`; for Slack the value is `slack`.
- Use internal IDs for URLs and management APIs.
- The deployment-owned backend assigns stable opaque IDs. Spritz only
round-trips them and must not generate or parse them.
- Model provider installation, internal connection, Spritz runtime backing,
and channel routes as separate concepts.
- Do not add core `targetType`, `preset`, or `runtime` fields.
Expand Down
Loading
Loading