feat(infra/domains): 域名池 + CDN 前置 + 签名端点分发 (tsk_NU9JuUweHWMt)
- domains.md: 四组域名隔离登记 + 冷备池 ≥5 + 启用流程(不含身份信息) - cdn/terraform: Cloudflare 配置即代码(WAF/bot/速率限制/代理DNS/回源鉴权注入)+ 30min 重放 Runbook - server/internal/originauth: 回源鉴权中间件,非 CDN 网段或鉴权头不符一律 403,支持双值轮换 - tools/endpoint-signer: 离线 Ed25519 签名 CLI(端点 + 公告文档,单调版本防回滚,key_id 双公钥轮换) - tools/publish-mirrors: ≥3 镜像发布 + hash 一致性校验 + 故障转移取回 - CLIENT-CONTRACT.md: schema/验签/防回滚/合并/兜底链/channel 客户端契约 - 出站独立出口要求写入部署文档;私钥/token/身份信息一律不入库 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
// Package notice defines the announcement payload that is published as the
|
||||
// signed static mirror of the API's /v1/notices endpoint (doc/05 §5 应急广播).
|
||||
//
|
||||
// Notices are read by the client's announcement slot and MUST be verified with
|
||||
// the embedded Ed25519 public key before display, exactly like endpoint
|
||||
// documents — they share the sign.Envelope.
|
||||
package notice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Valid severity levels.
|
||||
const (
|
||||
LevelInfo = "info"
|
||||
LevelWarning = "warning"
|
||||
LevelCritical = "critical"
|
||||
)
|
||||
|
||||
// Notice is a single bilingual announcement.
|
||||
type Notice struct {
|
||||
ID string `json:"id"`
|
||||
Level string `json:"level"`
|
||||
TitleZH string `json:"title_zh"`
|
||||
TitleEn string `json:"title_en"`
|
||||
BodyZH string `json:"body_zh"`
|
||||
BodyEn string `json:"body_en"`
|
||||
URL string `json:"url,omitempty"`
|
||||
PublishedAt string `json:"published_at"` // RFC3339 UTC
|
||||
}
|
||||
|
||||
// List is the payload signed into a notices document.
|
||||
type List struct {
|
||||
Notices []Notice `json:"notices"`
|
||||
}
|
||||
|
||||
func validLevel(l string) bool {
|
||||
switch l {
|
||||
case LevelInfo, LevelWarning, LevelCritical:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Validate checks a single notice for completeness and a known severity.
|
||||
func (n Notice) Validate() error {
|
||||
if strings.TrimSpace(n.ID) == "" {
|
||||
return fmt.Errorf("notice: id is required")
|
||||
}
|
||||
if !validLevel(n.Level) {
|
||||
return fmt.Errorf("notice %q: level must be one of info|warning|critical, got %q", n.ID, n.Level)
|
||||
}
|
||||
if strings.TrimSpace(n.TitleZH) == "" || strings.TrimSpace(n.TitleEn) == "" {
|
||||
return fmt.Errorf("notice %q: both title_zh and title_en are required", n.ID)
|
||||
}
|
||||
if strings.TrimSpace(n.PublishedAt) == "" {
|
||||
return fmt.Errorf("notice %q: published_at is required", n.ID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate checks the whole list and rejects duplicate IDs.
|
||||
func (l List) Validate() error {
|
||||
seen := map[string]bool{}
|
||||
for _, n := range l.Notices {
|
||||
if err := n.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if seen[n.ID] {
|
||||
return fmt.Errorf("notice: duplicate id %q", n.ID)
|
||||
}
|
||||
seen[n.ID] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user