diff --git a/Cargo.lock b/Cargo.lock index 2f423b1..b716648 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -713,7 +713,7 @@ dependencies = [ [[package]] name = "krowmail-core" -version = "0.4.0" +version = "0.4.1" dependencies = [ "base64 0.22.1", "ed25519-dalek", @@ -726,7 +726,7 @@ dependencies = [ [[package]] name = "krowmail-mcp" -version = "0.4.0" +version = "0.4.1" dependencies = [ "reqwest", "serde_json", @@ -734,7 +734,7 @@ dependencies = [ [[package]] name = "krowmail-server" -version = "0.4.0" +version = "0.4.1" dependencies = [ "axum", "chrono", diff --git a/Cargo.toml b/Cargo.toml index b0bdb83..8741fa9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ members = [ ] [workspace.package] -version = "0.4.0" +version = "0.4.1" edition = "2021" license = "Apache-2.0" repository = "https://github.com/Krow-labs/krowmail" diff --git a/crates/krowmail-core/src/policy.rs b/crates/krowmail-core/src/policy.rs index 7c6c895..6254a11 100644 --- a/crates/krowmail-core/src/policy.rs +++ b/crates/krowmail-core/src/policy.rs @@ -1,7 +1,14 @@ -pub const MAX_CROSS_TEAM_SENDS: i64 = 10; +/// 跨小组短窗硬限:同一发件人在 [`CROSS_TEAM_WINDOW_MINUTES`] 内发给同一收件小组的信数上限。 +/// 0.4.1 由 10 提到 100(与日限额同比例放宽);低于它的「连发先升级复核」软阈由各实现自定。 +pub const MAX_CROSS_TEAM_SENDS: i64 = 100; pub const CROSS_TEAM_WINDOW_MINUTES: i32 = 10; -pub const MAX_DAILY_OWNER_PAIR: i64 = 50; -pub const MAX_HOURLY_EXTERNAL: i64 = 30; +/// 跨主人日限额:滚动 24 小时内,**同一发件主人名下全部小组**发给同一收件主人的信合计上限 +/// (按主人对聚合,不按组员、不按小组)。0.4.1 由 50 提到 1000:同一组织内多小组密集协同 +/// 时 50 封半天即满,把正常往来当成了滥用。 +pub const MAX_DAILY_OWNER_PAIR: i64 = 1000; +/// 外部来信小时限:收件方(实际承载者)一小时内收到的外部来信上限。0.4.1 由 30 提到 300: +/// 日限额放到 1000 后,30/小时会先于它成为密集协同的墙。 +pub const MAX_HOURLY_EXTERNAL: i64 = 300; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum MailPolicy { diff --git a/spec/s2s.openapi.yaml b/spec/s2s.openapi.yaml index 79e30a1..710003f 100644 --- a/spec/s2s.openapi.yaml +++ b/spec/s2s.openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: Krowmail server-to-server - version: 0.4.0 + version: 0.4.1 description: > 发件服务器向收件域名登记的端点提交信封。签名用 HTTP Message Signatures (RFC 9421)的一个固定子集,算法是 Ed25519。keyid 形如 `域名/keyid`。 @@ -15,6 +15,9 @@ info: 0.4.0:信封新增可选的 from_numeric(发件地址的编号形,组员号#小组号#主人号@域,与 from 同一 发件人且同域)。编号永不复用,随整体签名过域后收件方拿到一个不随改名漂移的身份锚; 不带 = 收件方只有名字形、按弱锚处理,带了但半截(名字段 / 异域)→ 拒收。 + 0.4.1:三个政策限速常量同比例放宽——MAX_DAILY_OWNER_PAIR(滚动 24 小时、按「发件主人 → + 收件主人」整对聚合的跨主人日限额)50 → 1000;MAX_HOURLY_EXTERNAL(收件方每小时外部来信) + 30 → 300;MAX_CROSS_TEAM_SENDS(同一发件人 10 分钟内发给同一小组)10 → 100。信封与端点不变。 paths: /krowmail/v1/inbound: post: diff --git a/spec/test-vectors/address.json b/spec/test-vectors/address.json index fc857ee..f135c57 100644 --- a/spec/test-vectors/address.json +++ b/spec/test-vectors/address.json @@ -168,21 +168,39 @@ { "id": "decide-rate-daily", "fn": "decide", - "input": { "same_owner": false, "flat_flag": false, "cross_open": true, "policy": "everyone", "known_contact": false, "blocked": false, "recent_to_team": 0, "daily_pair": 50, "hourly_inbox": 0 }, + "input": { "same_owner": false, "flat_flag": false, "cross_open": true, "policy": "everyone", "known_contact": false, "blocked": false, "recent_to_team": 0, "daily_pair": 1000, "hourly_inbox": 0 }, "expect": { "decision": "rate_daily" } }, + { + "id": "decide-rate-daily-below-cap", + "fn": "decide", + "input": { "same_owner": false, "flat_flag": false, "cross_open": true, "policy": "everyone", "known_contact": false, "blocked": false, "recent_to_team": 0, "daily_pair": 999, "hourly_inbox": 0 }, + "expect": { "decision": "allow", "trust": "external" } + }, { "id": "decide-rate-hourly", "fn": "decide", - "input": { "same_owner": false, "flat_flag": false, "cross_open": true, "policy": "everyone", "known_contact": false, "blocked": false, "recent_to_team": 0, "daily_pair": 0, "hourly_inbox": 30 }, + "input": { "same_owner": false, "flat_flag": false, "cross_open": true, "policy": "everyone", "known_contact": false, "blocked": false, "recent_to_team": 0, "daily_pair": 0, "hourly_inbox": 300 }, "expect": { "decision": "rate_hourly" } }, + { + "id": "decide-rate-hourly-below-cap", + "fn": "decide", + "input": { "same_owner": false, "flat_flag": false, "cross_open": true, "policy": "everyone", "known_contact": false, "blocked": false, "recent_to_team": 0, "daily_pair": 0, "hourly_inbox": 299 }, + "expect": { "decision": "allow", "trust": "external" } + }, { "id": "decide-rate-team", "fn": "decide", - "input": { "same_owner": false, "flat_flag": false, "cross_open": true, "policy": "everyone", "known_contact": false, "blocked": false, "recent_to_team": 10, "daily_pair": 0, "hourly_inbox": 0 }, + "input": { "same_owner": false, "flat_flag": false, "cross_open": true, "policy": "everyone", "known_contact": false, "blocked": false, "recent_to_team": 100, "daily_pair": 0, "hourly_inbox": 0 }, "expect": { "decision": "rate_team" } }, + { + "id": "decide-rate-team-below-cap", + "fn": "decide", + "input": { "same_owner": false, "flat_flag": false, "cross_open": true, "policy": "everyone", "known_contact": false, "blocked": false, "recent_to_team": 99, "daily_pair": 0, "hourly_inbox": 0 }, + "expect": { "decision": "allow", "trust": "external" } + }, { "id": "decide-same-owner", "fn": "decide",