From 54180c5eb07c38b6b87e79e5d50452ccc4c19a93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BF=98=E7=8F=A0=E6=A5=BC=E4=B8=BB?= Date: Wed, 21 Oct 2026 06:22:24 +0000 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=E8=B7=A8=E4=B8=BB=E4=BA=BA=E6=97=A5?= =?UTF-8?q?=E9=99=90=E9=A2=9D=20MAX=5FDAILY=5FOWNER=5FPAIR=2050=20?= =?UTF-8?q?=E2=86=92=201000=EF=BC=880.4.1=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 限额语义是「滚动 24 小时内,同一发件主人名下全部小组发给同一收件主人的信合计」 (按主人对聚合,不按组员、不按小组)。同一 GitHub 组织内多小组密集协同时,50 封 半天即满,整个主人名下所有组员同时被 429 挡住,把正常往来当成了滥用。 - crates/krowmail-core/src/policy.rs:MAX_DAILY_OWNER_PAIR 50 → 1000,补语义注释。 decide() 逻辑、CrossInput、信封与端点均不变。 - spec/test-vectors/address.json:decide-rate-daily 的 daily_pair 50 → 1000;新增 decide-rate-daily-below-cap(999 → allow/external)钉边界。 - spec/s2s.openapi.yaml:info.version 0.4.1 + 版本说明一行。 - workspace 0.4.0 → 0.4.1(Cargo.toml / Cargo.lock)。 依据:道哥(Krow-labs 组织维护者)2026-10-21 14:15 Asia/Shanghai 直聊指令 「请把这个限额上调到 1000,组织内协同密度和频率是非常大的」。 下游 Krow-labs/krowworker 以 KROWMAIL_UPSTREAM_REF=v0.4.1 回灌 policy.rs。 --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- crates/krowmail-core/src/policy.rs | 5 ++++- spec/s2s.openapi.yaml | 4 +++- spec/test-vectors/address.json | 8 +++++++- 5 files changed, 18 insertions(+), 7 deletions(-) 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..e7654e3 100644 --- a/crates/krowmail-core/src/policy.rs +++ b/crates/krowmail-core/src/policy.rs @@ -1,6 +1,9 @@ pub const MAX_CROSS_TEAM_SENDS: i64 = 10; pub const CROSS_TEAM_WINDOW_MINUTES: i32 = 10; -pub const MAX_DAILY_OWNER_PAIR: i64 = 50; +/// 跨主人日限额:滚动 24 小时内,**同一发件主人名下全部小组**发给同一收件主人的信合计上限 +/// (按主人对聚合,不按组员、不按小组)。0.4.1 由 50 提到 1000:同一组织内多小组密集协同 +/// 时 50 封半天即满,把正常往来当成了滥用。 +pub const MAX_DAILY_OWNER_PAIR: i64 = 1000; pub const MAX_HOURLY_EXTERNAL: i64 = 30; #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/spec/s2s.openapi.yaml b/spec/s2s.openapi.yaml index 79e30a1..0362caa 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,8 @@ info: 0.4.0:信封新增可选的 from_numeric(发件地址的编号形,组员号#小组号#主人号@域,与 from 同一 发件人且同域)。编号永不复用,随整体签名过域后收件方拿到一个不随改名漂移的身份锚; 不带 = 收件方只有名字形、按弱锚处理,带了但半截(名字段 / 异域)→ 拒收。 + 0.4.1:政策常量 MAX_DAILY_OWNER_PAIR(滚动 24 小时、按「发件主人 → 收件主人」整对聚合的 + 跨主人日限额)由 50 提到 1000;信封与端点不变。 paths: /krowmail/v1/inbound: post: diff --git a/spec/test-vectors/address.json b/spec/test-vectors/address.json index fc857ee..f5d2db9 100644 --- a/spec/test-vectors/address.json +++ b/spec/test-vectors/address.json @@ -168,9 +168,15 @@ { "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", From a29481435988dce2f563949f955a1d8be28e7565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BF=98=E7=8F=A0=E6=A5=BC=E4=B8=BB?= Date: Wed, 21 Oct 2026 15:43:12 +0000 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=E5=B0=8F=E6=97=B6=E9=99=90=E4=B8=8E?= =?UTF-8?q?=E7=9F=AD=E7=AA=97=E7=A1=AC=E9=99=90=E5=90=8C=E6=AF=94=E4=BE=8B?= =?UTF-8?q?=E6=94=BE=E5=AE=BD=E2=80=94=E2=80=94MAX=5FHOURLY=5FEXTERNAL=203?= =?UTF-8?q?0=20=E2=86=92=20300=E3=80=81MAX=5FCROSS=5FTEAM=5FSENDS=2010=20?= =?UTF-8?q?=E2=86=92=20100?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 日限额放到 1000 后,收件方 30 封/小时与同一发件人 10 封/10 分钟会先于它成为密集协同的墙 (一位对外接口人同时接几个组织的来信、一名组员在多条线程里并行回信都会撞到)。 - policy.rs:MAX_HOURLY_EXTERNAL 300、MAX_CROSS_TEAM_SENDS 100,补语义注释;窗口 10 分钟不变。 - spec/test-vectors/address.json:decide-rate-hourly 30 → 300、decide-rate-team 10 → 100; 新增 decide-rate-hourly-below-cap(299 → allow)、decide-rate-team-below-cap(99 → allow)。 - spec/s2s.openapi.yaml:0.4.1 版本说明覆盖三个常量。 依据:道哥 2026-10-21 23:37 Asia/Shanghai 直聊「把每小时 30 封、10 分钟 10 封这两道墙也一并放宽」。 --- crates/krowmail-core/src/policy.rs | 8 ++++++-- spec/s2s.openapi.yaml | 5 +++-- spec/test-vectors/address.json | 16 ++++++++++++++-- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/crates/krowmail-core/src/policy.rs b/crates/krowmail-core/src/policy.rs index e7654e3..6254a11 100644 --- a/crates/krowmail-core/src/policy.rs +++ b/crates/krowmail-core/src/policy.rs @@ -1,10 +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; /// 跨主人日限额:滚动 24 小时内,**同一发件主人名下全部小组**发给同一收件主人的信合计上限 /// (按主人对聚合,不按组员、不按小组)。0.4.1 由 50 提到 1000:同一组织内多小组密集协同 /// 时 50 封半天即满,把正常往来当成了滥用。 pub const MAX_DAILY_OWNER_PAIR: i64 = 1000; -pub const MAX_HOURLY_EXTERNAL: i64 = 30; +/// 外部来信小时限:收件方(实际承载者)一小时内收到的外部来信上限。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 0362caa..710003f 100644 --- a/spec/s2s.openapi.yaml +++ b/spec/s2s.openapi.yaml @@ -15,8 +15,9 @@ info: 0.4.0:信封新增可选的 from_numeric(发件地址的编号形,组员号#小组号#主人号@域,与 from 同一 发件人且同域)。编号永不复用,随整体签名过域后收件方拿到一个不随改名漂移的身份锚; 不带 = 收件方只有名字形、按弱锚处理,带了但半截(名字段 / 异域)→ 拒收。 - 0.4.1:政策常量 MAX_DAILY_OWNER_PAIR(滚动 24 小时、按「发件主人 → 收件主人」整对聚合的 - 跨主人日限额)由 50 提到 1000;信封与端点不变。 + 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 f5d2db9..f135c57 100644 --- a/spec/test-vectors/address.json +++ b/spec/test-vectors/address.json @@ -180,15 +180,27 @@ { "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",