mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-07 08:27:12 +00:00
refactor(integrations): make chat integrations Slack-only
This commit is contained in:
parent
1ff30ea03c
commit
8ad14faa17
27 changed files with 180 additions and 246 deletions
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -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.<provider>]`; 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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.<name>.<provider>]`
|
||||
- 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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::{
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<StringOrSplice>,
|
||||
/// Provider-specific destination subtables. First-pass chat providers.
|
||||
/// Provider-specific destination subtables.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub slack: Option<NotificationProviderLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub discord: Option<NotificationProviderLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub teams: Option<NotificationProviderLayer>,
|
||||
}
|
||||
|
||||
/// A single string array entry that may be the splice marker.
|
||||
|
|
@ -386,10 +382,6 @@ pub struct InterviewsLayer {
|
|||
pub provider: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub slack: Option<InterviewProviderLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub discord: Option<InterviewProviderLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub teams: Option<InterviewProviderLayer>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||
|
|
|
|||
|
|
@ -189,21 +189,15 @@ pub struct ServerLoggingLayer {
|
|||
pub destination: Option<LogDestination>,
|
||||
}
|
||||
|
||||
/// `[server.integrations.<provider>]` — 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.<provider>]` — 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<GithubIntegrationLayer>,
|
||||
pub github: Option<GithubIntegrationLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub slack: Option<SlackIntegrationLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub discord: Option<DiscordIntegrationLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub teams: Option<TeamsIntegrationLayer>,
|
||||
pub slack: Option<SlackIntegrationLayer>,
|
||||
}
|
||||
|
||||
/// `[server.integrations.github]` — GitHub App, credentials, and inbound
|
||||
|
|
@ -235,22 +229,6 @@ pub struct SlackIntegrationLayer {
|
|||
pub default_channel: Option<InterpString>,
|
||||
}
|
||||
|
||||
/// `[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<bool>,
|
||||
}
|
||||
|
||||
/// `[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<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, fabro_macros::Combine)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct IntegrationWebhooksLayer {
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<ResolveError>,
|
||||
) -> 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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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::<SettingsLayer>()
|
||||
.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::<SettingsLayer>()
|
||||
.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(
|
||||
|
|
|
|||
|
|
@ -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::<SettingsLayer>()
|
||||
.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(
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -302,8 +302,6 @@ pub struct NotificationRouteSettings {
|
|||
pub provider: Option<String>,
|
||||
pub events: Vec<String>,
|
||||
pub slack: Option<NotificationProviderSettings>,
|
||||
pub discord: Option<NotificationProviderSettings>,
|
||||
pub teams: Option<NotificationProviderSettings>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||
|
|
@ -315,8 +313,6 @@ pub struct NotificationProviderSettings {
|
|||
pub struct RunInterviewsSettings {
|
||||
pub provider: Option<String>,
|
||||
pub slack: Option<InterviewProviderSettings>,
|
||||
pub discord: Option<InterviewProviderSettings>,
|
||||
pub teams: Option<InterviewProviderSettings>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||
|
|
|
|||
|
|
@ -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<InterpString>,
|
||||
}
|
||||
|
||||
#[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<WebhookStrategy>,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -22,7 +22,5 @@ export interface NotificationRouteSettings {
|
|||
'provider': string | null;
|
||||
'events': Array<string>;
|
||||
'slack': NotificationProviderSettings | null;
|
||||
'discord': NotificationProviderSettings | null;
|
||||
'teams': NotificationProviderSettings | null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
Loading…
Add table
Reference in a new issue