diff --git a/apps/fabro-web/app/routes/workflow-detail.tsx b/apps/fabro-web/app/routes/workflow-detail.tsx index fe5ac189e..c4a7764eb 100644 --- a/apps/fabro-web/app/routes/workflow-detail.tsx +++ b/apps/fabro-web/app/routes/workflow-detail.tsx @@ -84,7 +84,7 @@ function sampleSettings({ }, }, notifications: {}, - interviews: { provider: null, slack: null, discord: null, teams: null }, + interviews: { provider: null, slack: null }, agent: { permissions: null, mcps: {} }, hooks: [], scm: { provider: null, owner: null, repository: null, github: null }, diff --git a/docs/brainstorms/2026-04-08-settings-toml-redesign-requirements.md b/docs/brainstorms/2026-04-08-settings-toml-redesign-requirements.md index 412981a1e..6dd9f4c7f 100644 --- a/docs/brainstorms/2026-04-08-settings-toml-redesign-requirements.md +++ b/docs/brainstorms/2026-04-08-settings-toml-redesign-requirements.md @@ -108,7 +108,7 @@ The new design must optimize for: - R62. Sandbox config must remain provider-specific because provider differences are too large to hide behind one flat abstraction. - R63. Model config must remain intentionally provider-neutral. It should not grow provider-specific subtables. `run.model.fallbacks` is a single ordered array of model references. Each entry may be a bare provider token such as `openai`, a bare model alias or model id such as `gpt-5.4`, or a qualified reference such as `gemini/gemini-flash`. Bare references are allowed only when unambiguous. Ambiguous bare references must hard-error and require qualification. A bare provider token means “choose the best matching model from that provider.” - R64. SCM config must be provider-neutral at the core (`[run.scm]`) with room for provider-specific nested tables such as `[run.scm.github]` only where necessary. -- R65. Chat platforms such as Slack, Discord, and Teams are integrations. Their server-owned setup lives under `[server.integrations.]`; run behavior lives under `[run.notifications.*]` and `[run.interviews]`. +- R65. Slack is the chat integration. Its server-owned setup lives under `[server.integrations.slack]`; run behavior lives under `[run.notifications.*]` and `[run.interviews]`. - R66. Object-store-backed domains must use a shared pattern: a small provider-neutral envelope plus provider-specific nested tables. - R67. For local object-store providers, default to `server.storage.root`, but allow explicit local override roots when needed. diff --git a/docs/plans/2026-04-08-settings-toml-redesign-implementation-plan.md b/docs/plans/2026-04-08-settings-toml-redesign-implementation-plan.md index c20894061..18da2ae90 100644 --- a/docs/plans/2026-04-08-settings-toml-redesign-implementation-plan.md +++ b/docs/plans/2026-04-08-settings-toml-redesign-implementation-plan.md @@ -75,7 +75,7 @@ This refactor is centered on four seams: - notification route surface first pass: - route envelope fields are `enabled`, `provider`, and `events` - provider-specific destination fields live under `[run.notifications..]` - - first-pass chat destinations for Slack, Discord, and Teams use `channel` + - first-pass Slack destinations use `channel` - duration parser first pass: - one shared parser accepts a single unit suffix per value: `ms`, `s`, `m`, `h`, or `d` - composed values like `1h30m` are not supported in first pass; use the smallest needed unit instead diff --git a/docs/plans/2026-04-09-settings-toml-redesign-handoff.md b/docs/plans/2026-04-09-settings-toml-redesign-handoff.md index 4a4206125..f6b92b790 100644 --- a/docs/plans/2026-04-09-settings-toml-redesign-handoff.md +++ b/docs/plans/2026-04-09-settings-toml-redesign-handoff.md @@ -560,7 +560,7 @@ These are lessons learned during Stages 1–5. Save yourself the pain. enumerated list of known-provider subfields instead (that's why `RunSandboxLayer`, `NotificationRouteLayer`, `InterviewsLayer`, `RunScmLayer`, `ServerIntegrationsLayer`, etc. have explicit - `github`/`slack`/`discord`/`teams`/`local`/`s3` fields). Adding a new + `github`/`slack`/`local`/`s3` fields). Adding a new provider means adding a new field. ## Repo conventions you'll hit diff --git a/docs/public/api-reference/fabro-api.yaml b/docs/public/api-reference/fabro-api.yaml index 40a8f1a77..2a98b78cc 100644 --- a/docs/public/api-reference/fabro-api.yaml +++ b/docs/public/api-reference/fabro-api.yaml @@ -7771,16 +7771,12 @@ components: ServerIntegrationsSettings: type: object - required: [github, slack, discord, teams] + required: [github, slack] properties: github: $ref: "#/components/schemas/GithubIntegrationSettings" slack: $ref: "#/components/schemas/SlackIntegrationSettings" - discord: - $ref: "#/components/schemas/DiscordIntegrationSettings" - teams: - $ref: "#/components/schemas/TeamsIntegrationSettings" GithubIntegrationSettings: type: object @@ -7820,20 +7816,6 @@ components: default_channel: type: ["string", "null"] - DiscordIntegrationSettings: - type: object - required: [enabled] - properties: - enabled: - type: boolean - - TeamsIntegrationSettings: - type: object - required: [enabled] - properties: - enabled: - type: boolean - IntegrationWebhooksSettings: type: object required: [strategy, ip_allowlist] @@ -8218,7 +8200,7 @@ components: NotificationRouteSettings: type: object - required: [enabled, provider, events, slack, discord, teams] + required: [enabled, provider, events, slack] properties: enabled: type: boolean @@ -8232,14 +8214,6 @@ components: oneOf: - $ref: "#/components/schemas/NotificationProviderSettings" - type: "null" - discord: - oneOf: - - $ref: "#/components/schemas/NotificationProviderSettings" - - type: "null" - teams: - oneOf: - - $ref: "#/components/schemas/NotificationProviderSettings" - - type: "null" NotificationProviderSettings: type: object @@ -8250,7 +8224,7 @@ components: RunInterviewsSettings: type: object - required: [provider, slack, discord, teams] + required: [provider, slack] properties: provider: type: ["string", "null"] @@ -8258,14 +8232,6 @@ components: oneOf: - $ref: "#/components/schemas/InterviewProviderSettings" - type: "null" - discord: - oneOf: - - $ref: "#/components/schemas/InterviewProviderSettings" - - type: "null" - teams: - oneOf: - - $ref: "#/components/schemas/InterviewProviderSettings" - - type: "null" InterviewProviderSettings: type: object diff --git a/lib/crates/fabro-api/build.rs b/lib/crates/fabro-api/build.rs index bb29e8d8b..98018abc2 100644 --- a/lib/crates/fabro-api/build.rs +++ b/lib/crates/fabro-api/build.rs @@ -315,16 +315,6 @@ fn main() { "fabro_types::settings::server::SlackIntegrationSettings", &[], ), - ( - "DiscordIntegrationSettings", - "fabro_types::settings::server::DiscordIntegrationSettings", - &[], - ), - ( - "TeamsIntegrationSettings", - "fabro_types::settings::server::TeamsIntegrationSettings", - &[], - ), ( "IntegrationWebhooksSettings", "fabro_types::settings::server::IntegrationWebhooksSettings", diff --git a/lib/crates/fabro-api/src/lib.rs b/lib/crates/fabro-api/src/lib.rs index 6c393793f..a8d45b141 100644 --- a/lib/crates/fabro-api/src/lib.rs +++ b/lib/crates/fabro-api/src/lib.rs @@ -16,13 +16,13 @@ mod generated { pub mod types { pub use fabro_model::{Model, ModelCosts, ModelFeatures, ModelLimits, ModelTestMode, Provider}; pub use fabro_types::settings::server::{ - DiscordIntegrationSettings, GithubIntegrationSettings, GithubIntegrationStrategy, - IntegrationWebhooksSettings, IpAllowEntry, LogDestination, ObjectStoreSettings, - ServerApiSettings, ServerArtifactsSettings, ServerAuthGithubSettings, ServerAuthMethod, - ServerAuthSettings, ServerIntegrationsSettings, ServerIpAllowlistOverrideSettings, - ServerIpAllowlistSettings, ServerListenSettings, ServerLoggingSettings, - ServerSchedulerSettings, ServerSlateDbSettings, ServerStorageSettings, ServerWebSettings, - SlackIntegrationSettings, TeamsIntegrationSettings, WebhookStrategy, + GithubIntegrationSettings, GithubIntegrationStrategy, IntegrationWebhooksSettings, + IpAllowEntry, LogDestination, ObjectStoreSettings, ServerApiSettings, + ServerArtifactsSettings, ServerAuthGithubSettings, ServerAuthMethod, ServerAuthSettings, + ServerIntegrationsSettings, ServerIpAllowlistOverrideSettings, ServerIpAllowlistSettings, + ServerListenSettings, ServerLoggingSettings, ServerSchedulerSettings, + ServerSlateDbSettings, ServerStorageSettings, ServerWebSettings, SlackIntegrationSettings, + WebhookStrategy, }; pub use fabro_types::settings::{FeaturesNamespace, ServerNamespace}; pub use fabro_types::status::{ diff --git a/lib/crates/fabro-cli/tests/it/cmd/attach.rs b/lib/crates/fabro-cli/tests/it/cmd/attach.rs index 664ef0f87..d40995118 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/attach.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/attach.rs @@ -945,10 +945,8 @@ fn attach_json_errors_without_prompting_for_human_input() { } }, "interviews": { - "discord": null, "provider": null, - "slack": null, - "teams": null + "slack": null }, "metadata": {}, "model": { diff --git a/lib/crates/fabro-config/src/layers/mod.rs b/lib/crates/fabro-config/src/layers/mod.rs index 90687b6d2..9d35995d6 100644 --- a/lib/crates/fabro-config/src/layers/mod.rs +++ b/lib/crates/fabro-config/src/layers/mod.rs @@ -29,12 +29,11 @@ pub use run::{ ScmGitHubLayer, StringOrSplice, }; pub use server::{ - DiscordIntegrationLayer, GithubIntegrationLayer, IntegrationWebhooksLayer, - ObjectStoreLocalLayer, ObjectStoreS3Layer, ServerApiLayer, ServerArtifactsLayer, - ServerAuthGithubLayer, ServerAuthLayer, ServerIntegrationsLayer, ServerIpAllowlistLayer, - ServerIpAllowlistOverrideLayer, ServerLayer, ServerListenLayer, ServerLoggingLayer, - ServerSchedulerLayer, ServerSlateDbLayer, ServerStorageLayer, ServerWebLayer, - SlackIntegrationLayer, TeamsIntegrationLayer, + GithubIntegrationLayer, IntegrationWebhooksLayer, ObjectStoreLocalLayer, ObjectStoreS3Layer, + ServerApiLayer, ServerArtifactsLayer, ServerAuthGithubLayer, ServerAuthLayer, + ServerIntegrationsLayer, ServerIpAllowlistLayer, ServerIpAllowlistOverrideLayer, ServerLayer, + ServerListenLayer, ServerLoggingLayer, ServerSchedulerLayer, ServerSlateDbLayer, + ServerStorageLayer, ServerWebLayer, SlackIntegrationLayer, }; pub(crate) use settings::SettingsLayer; pub use workflow::WorkflowLayer; diff --git a/lib/crates/fabro-config/src/layers/run.rs b/lib/crates/fabro-config/src/layers/run.rs index 0c6dfa504..aad3e97dd 100644 --- a/lib/crates/fabro-config/src/layers/run.rs +++ b/lib/crates/fabro-config/src/layers/run.rs @@ -334,13 +334,9 @@ pub struct NotificationRouteLayer { /// Raw Fabro event names. Splice marker supported at layering time. #[serde(default, skip_serializing_if = "Vec::is_empty")] pub events: Vec, - /// Provider-specific destination subtables. First-pass chat providers. + /// Provider-specific destination subtables. #[serde(default, skip_serializing_if = "Option::is_none")] pub slack: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub discord: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub teams: Option, } /// A single string array entry that may be the splice marker. @@ -386,10 +382,6 @@ pub struct InterviewsLayer { pub provider: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub slack: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub discord: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub teams: Option, } #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] diff --git a/lib/crates/fabro-config/src/layers/server.rs b/lib/crates/fabro-config/src/layers/server.rs index f3a6f9a2c..bc03a1824 100644 --- a/lib/crates/fabro-config/src/layers/server.rs +++ b/lib/crates/fabro-config/src/layers/server.rs @@ -189,21 +189,15 @@ pub struct ServerLoggingLayer { pub destination: Option, } -/// `[server.integrations.]` — cohesive integration surface for chat -/// platforms and git providers (GitHub App, webhooks, etc.). First-pass -/// integrations enumerate known providers rather than using a flatten-HashMap -/// shape so strict unknown-field validation still holds. +/// `[server.integrations.]` — cohesive integration surface for Slack +/// and git providers (GitHub App, webhooks, etc.). #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, fabro_macros::Combine)] #[serde(deny_unknown_fields)] pub struct ServerIntegrationsLayer { #[serde(default, skip_serializing_if = "Option::is_none")] - pub github: Option, + pub github: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub slack: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub discord: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub teams: Option, + pub slack: Option, } /// `[server.integrations.github]` — GitHub App, credentials, and inbound @@ -235,22 +229,6 @@ pub struct SlackIntegrationLayer { pub default_channel: Option, } -/// `[server.integrations.discord]` — Discord workspace configuration. -#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, fabro_macros::Combine)] -#[serde(deny_unknown_fields)] -pub struct DiscordIntegrationLayer { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub enabled: Option, -} - -/// `[server.integrations.teams]` — Microsoft Teams configuration. -#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, fabro_macros::Combine)] -#[serde(deny_unknown_fields)] -pub struct TeamsIntegrationLayer { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub enabled: Option, -} - #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, fabro_macros::Combine)] #[serde(deny_unknown_fields)] pub struct IntegrationWebhooksLayer { diff --git a/lib/crates/fabro-config/src/lib.rs b/lib/crates/fabro-config/src/lib.rs index bc5c98fe5..076a5ce7e 100644 --- a/lib/crates/fabro-config/src/lib.rs +++ b/lib/crates/fabro-config/src/lib.rs @@ -40,19 +40,18 @@ pub use input_overrides::{InputOverrideParseError, parse_input_overrides}; pub use layers::{ CliAuthLayer, CliExecAgentLayer, CliExecLayer, CliExecModelLayer, CliLayer, CliLoggingLayer, CliOutputLayer, CliTargetLayer, CliUpdatesLayer, DaytonaDockerfileLayer, DaytonaSandboxLayer, - DaytonaSnapshotLayer, DiscordIntegrationLayer, DockerSandboxLayer, FeaturesLayer, - GitAuthorLayer, GithubIntegrationLayer, HookAgentMarker, HookEntry, HookTlsMode, - IntegrationWebhooksLayer, InterviewProviderLayer, InterviewsLayer, LogFilter, McpEntryLayer, - MergeMap, ModelRefOrSplice, NotificationProviderLayer, NotificationRouteLayer, - ObjectStoreLocalLayer, ObjectStoreS3Layer, PrepareStep, ProjectLayer, ReplaceMap, - RunAgentLayer, RunArtifactsLayer, RunCheckpointLayer, RunExecutionLayer, RunGitLayer, - RunGoalLayer, RunIntegrationsGithubLayer, RunIntegrationsLayer, RunLayer, RunModelLayer, - RunPrepareLayer, RunPullRequestLayer, RunSandboxLayer, RunScmLayer, ScmGitHubLayer, - ServerApiLayer, ServerArtifactsLayer, ServerAuthGithubLayer, ServerAuthLayer, + DaytonaSnapshotLayer, DockerSandboxLayer, FeaturesLayer, GitAuthorLayer, + GithubIntegrationLayer, HookAgentMarker, HookEntry, HookTlsMode, IntegrationWebhooksLayer, + InterviewProviderLayer, InterviewsLayer, LogFilter, McpEntryLayer, MergeMap, ModelRefOrSplice, + NotificationProviderLayer, NotificationRouteLayer, ObjectStoreLocalLayer, ObjectStoreS3Layer, + PrepareStep, ProjectLayer, ReplaceMap, RunAgentLayer, RunArtifactsLayer, RunCheckpointLayer, + RunExecutionLayer, RunGitLayer, RunGoalLayer, RunIntegrationsGithubLayer, RunIntegrationsLayer, + RunLayer, RunModelLayer, RunPrepareLayer, RunPullRequestLayer, RunSandboxLayer, RunScmLayer, + ScmGitHubLayer, ServerApiLayer, ServerArtifactsLayer, ServerAuthGithubLayer, ServerAuthLayer, ServerIntegrationsLayer, ServerIpAllowlistLayer, ServerIpAllowlistOverrideLayer, ServerLayer, ServerListenLayer, ServerLoggingLayer, ServerSchedulerLayer, ServerSlateDbLayer, ServerStorageLayer, ServerWebLayer, SlackIntegrationLayer, StickyMap, StringOrSplice, - TeamsIntegrationLayer, WorkflowLayer, + WorkflowLayer, }; pub(crate) use layers::{Combine, SettingsLayer}; pub use logging::{resolve_log_destination, resolve_log_destination_with_env}; diff --git a/lib/crates/fabro-config/src/resolve/run.rs b/lib/crates/fabro-config/src/resolve/run.rs index ebabadb7b..1a85289f2 100644 --- a/lib/crates/fabro-config/src/resolve/run.rs +++ b/lib/crates/fabro-config/src/resolve/run.rs @@ -242,8 +242,6 @@ fn resolve_notification_route(route: &NotificationRouteLayer) -> NotificationRou }) .collect(), slack: route.slack.as_ref().map(resolve_notification_provider), - discord: route.discord.as_ref().map(resolve_notification_provider), - teams: route.teams.as_ref().map(resolve_notification_provider), } } @@ -263,8 +261,6 @@ fn resolve_interviews(interviews: Option<&InterviewsLayer>) -> RunInterviewsSett RunInterviewsSettings { provider: interviews.provider.clone(), slack: interviews.slack.as_ref().map(resolve_interview_provider), - discord: interviews.discord.as_ref().map(resolve_interview_provider), - teams: interviews.teams.as_ref().map(resolve_interview_provider), } } diff --git a/lib/crates/fabro-config/src/resolve/server.rs b/lib/crates/fabro-config/src/resolve/server.rs index 902b1ff3c..f6c22c699 100644 --- a/lib/crates/fabro-config/src/resolve/server.rs +++ b/lib/crates/fabro-config/src/resolve/server.rs @@ -1,12 +1,12 @@ use fabro_types::settings::InterpString; use fabro_types::settings::server::{ - DiscordIntegrationSettings, GithubIntegrationSettings, GithubIntegrationStrategy, - IntegrationWebhooksSettings, IpAllowEntry, ObjectStoreProvider, ObjectStoreSettings, - ServerApiSettings, ServerArtifactsSettings, ServerAuthGithubSettings, ServerAuthMethod, - ServerAuthSettings, ServerIntegrationsSettings, ServerIpAllowlistOverrideSettings, - ServerIpAllowlistSettings, ServerListenSettings, ServerLoggingSettings, ServerNamespace, - ServerSchedulerSettings, ServerSlateDbSettings, ServerStorageSettings, ServerWebSettings, - SlackIntegrationSettings, TeamsIntegrationSettings, WebhookStrategy, + GithubIntegrationSettings, GithubIntegrationStrategy, IntegrationWebhooksSettings, + IpAllowEntry, ObjectStoreProvider, ObjectStoreSettings, ServerApiSettings, + ServerArtifactsSettings, ServerAuthGithubSettings, ServerAuthMethod, ServerAuthSettings, + ServerIntegrationsSettings, ServerIpAllowlistOverrideSettings, ServerIpAllowlistSettings, + ServerListenSettings, ServerLoggingSettings, ServerNamespace, ServerSchedulerSettings, + ServerSlateDbSettings, ServerStorageSettings, ServerWebSettings, SlackIntegrationSettings, + WebhookStrategy, }; use fabro_util::Home; @@ -454,7 +454,7 @@ fn resolve_integrations( errors: &mut Vec, ) -> ServerIntegrationsSettings { ServerIntegrationsSettings { - github: layer + github: layer .and_then(|integrations| integrations.github.as_ref()) .map(|github| GithubIntegrationSettings { enabled: github.enabled.unwrap_or(true), @@ -467,25 +467,13 @@ fn resolve_integrations( }), }) .unwrap_or_default(), - slack: layer + slack: layer .and_then(|integrations| integrations.slack.as_ref()) .map(|slack| SlackIntegrationSettings { enabled: slack.enabled.unwrap_or(true), default_channel: slack.default_channel.clone(), }) .unwrap_or_default(), - discord: layer - .and_then(|integrations| integrations.discord.as_ref()) - .map(|discord| DiscordIntegrationSettings { - enabled: discord.enabled.unwrap_or(true), - }) - .unwrap_or_default(), - teams: layer - .and_then(|integrations| integrations.teams.as_ref()) - .map(|teams| TeamsIntegrationSettings { - enabled: teams.enabled.unwrap_or(true), - }) - .unwrap_or_default(), } } diff --git a/lib/crates/fabro-config/src/tests/resolve_run.rs b/lib/crates/fabro-config/src/tests/resolve_run.rs index 469084602..7a0501082 100644 --- a/lib/crates/fabro-config/src/tests/resolve_run.rs +++ b/lib/crates/fabro-config/src/tests/resolve_run.rs @@ -26,6 +26,92 @@ fn resolves_run_defaults_from_empty_settings() { assert!(settings.pull_request.is_none()); } +#[test] +fn resolved_run_chat_surfaces_are_slack_only() { + let settings = WorkflowSettingsBuilder::from_toml( + r##" +_version = 1 + +[run.notifications.ops] +enabled = true +provider = "slack" +events = ["run.completed"] + +[run.notifications.ops.slack] +channel = "#ops" + +[run.interviews] +provider = "slack" + +[run.interviews.slack] +channel = "#ops" +"##, + ) + .expect("slack-only chat settings should resolve") + .run; + + let route = settings + .notifications + .get("ops") + .expect("notification route should resolve"); + + assert_eq!( + serde_json::to_value(route).expect("route should serialize"), + serde_json::json!({ + "enabled": true, + "provider": "slack", + "events": ["run.completed"], + "slack": { + "channel": "#ops", + }, + }) + ); + assert_eq!( + serde_json::to_value(&settings.interviews).expect("interviews should serialize"), + serde_json::json!({ + "provider": "slack", + "slack": { + "channel": "#ops", + }, + }) + ); +} + +#[test] +fn parsing_rejects_unknown_run_chat_destinations() { + let notifications = r##" +_version = 1 + +[run.notifications.ops.chatapp] +channel = "#ops" +"##; + + let err = notifications + .parse::() + .expect_err("unknown notification destination should be rejected"); + let message = err.to_string(); + assert!( + message.contains("chatapp") || message.contains("unknown field"), + "expected notification parse error for unknown chat provider, got: {message}" + ); + + let interviews = r##" +_version = 1 + +[run.interviews.chatapp] +channel = "#ops" +"##; + + let err = interviews + .parse::() + .expect_err("unknown interview destination should be rejected"); + let message = err.to_string(); + assert!( + message.contains("chatapp") || message.contains("unknown field"), + "expected interview parse error for unknown chat provider, got: {message}" + ); +} + #[test] fn resolves_explicit_stop_on_terminal_false() { let settings = WorkflowSettingsBuilder::from_toml( diff --git a/lib/crates/fabro-config/src/tests/resolve_server.rs b/lib/crates/fabro-config/src/tests/resolve_server.rs index a306a9800..7edb327c3 100644 --- a/lib/crates/fabro-config/src/tests/resolve_server.rs +++ b/lib/crates/fabro-config/src/tests/resolve_server.rs @@ -109,6 +109,51 @@ fn resolves_server_defaults_from_empty_settings() { assert!(!settings.slatedb.disk_cache); } +#[test] +fn resolved_server_integrations_are_slack_only_for_chat() { + let settings = resolve_server(&empty_settings_with_auth_methods()); + + let integrations = + serde_json::to_value(&settings.integrations).expect("integrations should serialize"); + + assert_eq!( + integrations, + serde_json::json!({ + "github": { + "enabled": false, + "strategy": "token", + "app_id": null, + "client_id": null, + "slug": null, + "webhooks": null, + }, + "slack": { + "enabled": false, + "default_channel": null, + }, + }) + ); +} + +#[test] +fn parsing_rejects_unknown_server_integrations() { + let source = r#" +_version = 1 + +[server.integrations.chatapp] +enabled = true +"#; + + let err = source + .parse::() + .expect_err("unknown chat integration should be rejected"); + let message = err.to_string(); + assert!( + message.contains("chatapp") || message.contains("unknown field"), + "expected parse error for unknown chat provider, got: {message}" + ); +} + #[test] fn resolves_server_logging_destination_from_settings() { let file = parse( diff --git a/lib/crates/fabro-redact/data/gitleaks.toml b/lib/crates/fabro-redact/data/gitleaks.toml index d4115601b..055e09305 100644 --- a/lib/crates/fabro-redact/data/gitleaks.toml +++ b/lib/crates/fabro-redact/data/gitleaks.toml @@ -338,26 +338,6 @@ description = "Uncovered a DigitalOcean OAuth Refresh Token, which could allow p regex = '''(?i)\b(dor_v1_[a-f0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' keywords = ["dor_v1_"] -[[rules]] -id = "discord-api-token" -description = "Detected a Discord API key, potentially compromising communication channels and user data privacy on Discord." -regex = '''(?i)[\w.-]{0,50}?(?:discord)(?:[ \t\w.-]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = ["discord"] - -[[rules]] -id = "discord-client-id" -description = "Identified a Discord client ID, which may lead to unauthorized integrations and data exposure in Discord applications." -regex = '''(?i)[\w.-]{0,50}?(?:discord)(?:[ \t\w.-]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([0-9]{18})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -entropy = 2 -keywords = ["discord"] - -[[rules]] -id = "discord-client-secret" -description = "Discovered a potential Discord client secret, risking compromised Discord bot integrations and data leaks." -regex = '''(?i)[\w.-]{0,50}?(?:discord)(?:[ \t\w.-]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -entropy = 2 -keywords = ["discord"] - [[rules]] id = "doppler-api-token" description = "Discovered a Doppler API token, posing a risk to environment and secrets management security." @@ -2479,16 +2459,6 @@ keywords = [ "message_bird", ] -[[rules]] -id = "microsoft-teams-webhook" -description = "Uncovered a Microsoft Teams Webhook, which could lead to unauthorized access to team collaboration tools and data leaks." -regex = '''https://[a-z0-9]+\.webhook\.office\.com/webhookb2/[a-z0-9]{8}-([a-z0-9]{4}-){3}[a-z0-9]{12}@[a-z0-9]{8}-([a-z0-9]{4}-){3}[a-z0-9]{12}/IncomingWebhook/[a-z0-9]{32}/[a-z0-9]{8}-([a-z0-9]{4}-){3}[a-z0-9]{12}''' -keywords = [ - "webhook.office.com", - "webhookb2", - "incomingwebhook", -] - [[rules]] id = "netlify-access-token" description = "Detected a Netlify Access Token, potentially compromising web hosting services and site management." @@ -3047,4 +3017,3 @@ id = "zendesk-secret-key" description = "Detected a Zendesk Secret Key, risking unauthorized access to customer support services and sensitive ticketing data." regex = '''(?i)[\w.-]{0,50}?(?:zendesk)(?:[ \t\w.-]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{40})(?:['|\"|\n|\r|\s|\x60|;]|$)''' keywords = ["zendesk"] - diff --git a/lib/crates/fabro-types/src/settings/mod.rs b/lib/crates/fabro-types/src/settings/mod.rs index 54b8c6aa1..388f3f10c 100644 --- a/lib/crates/fabro-types/src/settings/mod.rs +++ b/lib/crates/fabro-types/src/settings/mod.rs @@ -45,12 +45,12 @@ pub use run::{ ScmGitHubSettings, TlsMode, }; pub use server::{ - DiscordIntegrationSettings, GithubIntegrationSettings, IntegrationWebhooksSettings, - IpAllowEntry, LogDestination, ObjectStoreSettings, ServerApiSettings, ServerArtifactsSettings, - ServerAuthGithubSettings, ServerAuthMethod, ServerAuthSettings, ServerIntegrationsSettings, + GithubIntegrationSettings, IntegrationWebhooksSettings, IpAllowEntry, LogDestination, + ObjectStoreSettings, ServerApiSettings, ServerArtifactsSettings, ServerAuthGithubSettings, + ServerAuthMethod, ServerAuthSettings, ServerIntegrationsSettings, ServerIpAllowlistOverrideSettings, ServerIpAllowlistSettings, ServerListenSettings, ServerLoggingSettings, ServerNamespace, ServerSchedulerSettings, ServerSlateDbSettings, - ServerStorageSettings, ServerWebSettings, SlackIntegrationSettings, TeamsIntegrationSettings, + ServerStorageSettings, ServerWebSettings, SlackIntegrationSettings, }; pub use size::{ParseSizeError, Size}; pub use workflow::WorkflowNamespace; diff --git a/lib/crates/fabro-types/src/settings/run.rs b/lib/crates/fabro-types/src/settings/run.rs index e100575bc..9148d986c 100644 --- a/lib/crates/fabro-types/src/settings/run.rs +++ b/lib/crates/fabro-types/src/settings/run.rs @@ -302,8 +302,6 @@ pub struct NotificationRouteSettings { pub provider: Option, pub events: Vec, pub slack: Option, - pub discord: Option, - pub teams: Option, } #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] @@ -315,8 +313,6 @@ pub struct NotificationProviderSettings { pub struct RunInterviewsSettings { pub provider: Option, pub slack: Option, - pub discord: Option, - pub teams: Option, } #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] diff --git a/lib/crates/fabro-types/src/settings/server.rs b/lib/crates/fabro-types/src/settings/server.rs index 16a5718f9..fa92d3f64 100644 --- a/lib/crates/fabro-types/src/settings/server.rs +++ b/lib/crates/fabro-types/src/settings/server.rs @@ -258,10 +258,8 @@ pub struct ServerLoggingSettings { #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct ServerIntegrationsSettings { - pub github: GithubIntegrationSettings, - pub slack: SlackIntegrationSettings, - pub discord: DiscordIntegrationSettings, - pub teams: TeamsIntegrationSettings, + pub github: GithubIntegrationSettings, + pub slack: SlackIntegrationSettings, } #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] @@ -280,16 +278,6 @@ pub struct SlackIntegrationSettings { pub default_channel: Option, } -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub struct DiscordIntegrationSettings { - pub enabled: bool, -} - -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub struct TeamsIntegrationSettings { - pub enabled: bool, -} - #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct IntegrationWebhooksSettings { pub strategy: Option, diff --git a/lib/packages/fabro-api-client/src/.openapi-generator/FILES b/lib/packages/fabro-api-client/src/.openapi-generator/FILES index 946292b82..bc35c71ba 100644 --- a/lib/packages/fabro-api-client/src/.openapi-generator/FILES +++ b/lib/packages/fabro-api-client/src/.openapi-generator/FILES @@ -77,7 +77,6 @@ models/diff-file.ts models/diff-stats.ts models/diff-summary.ts models/dirty-status.ts -models/discord-integration-settings.ts models/disk-usage-response.ts models/disk-usage-run-row.ts models/disk-usage-summary-row.ts @@ -327,7 +326,6 @@ models/system-info-response.ts models/system-repair-run-issue.ts models/system-repair-runs-response.ts models/system-run-counts.ts -models/teams-integration-settings.ts models/terminal-status.ts models/timeline-entry-response.ts models/tls-mode.ts diff --git a/lib/packages/fabro-api-client/src/models/discord-integration-settings.ts b/lib/packages/fabro-api-client/src/models/discord-integration-settings.ts deleted file mode 100644 index 5ea86fd09..000000000 --- a/lib/packages/fabro-api-client/src/models/discord-integration-settings.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * Fabro Run API - * HTTP API for managing Fabro workflow run executions. - * - * The version of the OpenAPI document: 0.1.0 - * - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - - - -export interface DiscordIntegrationSettings { - 'enabled': boolean; -} - diff --git a/lib/packages/fabro-api-client/src/models/index.ts b/lib/packages/fabro-api-client/src/models/index.ts index ab156c5a1..e10cdb309 100644 --- a/lib/packages/fabro-api-client/src/models/index.ts +++ b/lib/packages/fabro-api-client/src/models/index.ts @@ -55,7 +55,6 @@ export * from './diff-file'; export * from './diff-stats'; export * from './diff-summary'; export * from './dirty-status'; -export * from './discord-integration-settings'; export * from './disk-usage-response'; export * from './disk-usage-run-row'; export * from './disk-usage-summary-row'; @@ -304,7 +303,6 @@ export * from './system-info-response'; export * from './system-repair-run-issue'; export * from './system-repair-runs-response'; export * from './system-run-counts'; -export * from './teams-integration-settings'; export * from './terminal-status'; export * from './timeline-entry-response'; export * from './tls-mode'; diff --git a/lib/packages/fabro-api-client/src/models/notification-route-settings.ts b/lib/packages/fabro-api-client/src/models/notification-route-settings.ts index 7acc95c38..85de1af5e 100644 --- a/lib/packages/fabro-api-client/src/models/notification-route-settings.ts +++ b/lib/packages/fabro-api-client/src/models/notification-route-settings.ts @@ -22,7 +22,5 @@ export interface NotificationRouteSettings { 'provider': string | null; 'events': Array; 'slack': NotificationProviderSettings | null; - 'discord': NotificationProviderSettings | null; - 'teams': NotificationProviderSettings | null; } diff --git a/lib/packages/fabro-api-client/src/models/run-interviews-settings.ts b/lib/packages/fabro-api-client/src/models/run-interviews-settings.ts index 2e935d62e..0caac4b57 100644 --- a/lib/packages/fabro-api-client/src/models/run-interviews-settings.ts +++ b/lib/packages/fabro-api-client/src/models/run-interviews-settings.ts @@ -20,7 +20,5 @@ import type { InterviewProviderSettings } from './interview-provider-settings'; export interface RunInterviewsSettings { 'provider': string | null; 'slack': InterviewProviderSettings | null; - 'discord': InterviewProviderSettings | null; - 'teams': InterviewProviderSettings | null; } diff --git a/lib/packages/fabro-api-client/src/models/server-integrations-settings.ts b/lib/packages/fabro-api-client/src/models/server-integrations-settings.ts index bee2ea73c..500877d6f 100644 --- a/lib/packages/fabro-api-client/src/models/server-integrations-settings.ts +++ b/lib/packages/fabro-api-client/src/models/server-integrations-settings.ts @@ -13,23 +13,15 @@ */ -// May contain unused imports in some cases -// @ts-ignore -import type { DiscordIntegrationSettings } from './discord-integration-settings'; // May contain unused imports in some cases // @ts-ignore import type { GithubIntegrationSettings } from './github-integration-settings'; // May contain unused imports in some cases // @ts-ignore import type { SlackIntegrationSettings } from './slack-integration-settings'; -// May contain unused imports in some cases -// @ts-ignore -import type { TeamsIntegrationSettings } from './teams-integration-settings'; export interface ServerIntegrationsSettings { 'github': GithubIntegrationSettings; 'slack': SlackIntegrationSettings; - 'discord': DiscordIntegrationSettings; - 'teams': TeamsIntegrationSettings; } diff --git a/lib/packages/fabro-api-client/src/models/teams-integration-settings.ts b/lib/packages/fabro-api-client/src/models/teams-integration-settings.ts deleted file mode 100644 index be2a971cb..000000000 --- a/lib/packages/fabro-api-client/src/models/teams-integration-settings.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * Fabro Run API - * HTTP API for managing Fabro workflow run executions. - * - * The version of the OpenAPI document: 0.1.0 - * - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - - - -export interface TeamsIntegrationSettings { - 'enabled': boolean; -} -