mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-14 10:13:54 +00:00
配置目录领域核心(spec #21 D3/D5,票 #25): - Domain/ 新增配置目录树:单父、显子序、无环;手动/订阅来源严格分离; 有效启用 = 节点及其全部祖先启用;手动节点携带子树移动与重排; 非空手动组递归删除(返回被删条目供 Keychain 清理);空组持久保留; 非法操作(跨来源、身份复用即共享父、成环、订阅节点结构改动、 订阅服务器直挂根)全部带明确原因拒绝且状态不动。 - 凭据引用:密码/插件参数只在 Keychain 存权威副本,配置树只持 CredentialReference;KeychainCredentialStore 写读一致、覆盖写、幂等删。 - CatalogFileStore:v2/catalog.json 目录 0700/文件 0600,临时文件原子替换; 缺失 → 空目录,损坏/版本未知/结构不一致(悬空、共享父、成环含根外孤环、 不可达)→ 命名明确的 corrupt,结构校验收敛在 ConfigurationCatalog.validated 工厂。 - 测试 43 项覆盖全部验收项;project.yml 增加 Domain/ 源码目录。 - .swiftlint.yml 关闭 trailing_comma(与 .swift-format 尾逗号基线冲突, 触发案例见文件内注释),README/AGENTS.md 同步 Domain/ 与工具说明。
50 lines
1.7 KiB
Swift
50 lines
1.7 KiB
Swift
import Foundation
|
|
|
|
/// 节点来源:手动子树与订阅子树严格分离(CONTEXT.md「Relationships and invariants」)。
|
|
enum NodeSource: String, Codable, Equatable, Sendable {
|
|
case manual
|
|
case subscription
|
|
}
|
|
|
|
/// 节点持久身份:不透明字符串。手动节点每次新建生成全新 UUID,身份不随
|
|
/// 显示名、位置或父节点变化,也不复用于其他逻辑节点;订阅节点使用供应商
|
|
/// 稳定 ID(作用域限本订阅,由订阅数据保证唯一),本票仅供夹具使用。
|
|
struct NodeID: Hashable, Codable, Sendable {
|
|
let rawValue: String
|
|
|
|
init(rawValue: String) { self.rawValue = rawValue }
|
|
|
|
/// 手动新建节点身份。
|
|
static func fresh() -> NodeID { NodeID(rawValue: UUID().uuidString) }
|
|
|
|
init(from decoder: Decoder) throws {
|
|
rawValue = try decoder.singleValueContainer().decode(String.self)
|
|
}
|
|
|
|
func encode(to encoder: Encoder) throws {
|
|
var container = encoder.singleValueContainer()
|
|
try container.encode(rawValue)
|
|
}
|
|
}
|
|
|
|
/// 凭据引用:指向 Keychain 权威副本的非秘密关联(CONTEXT.md「Credential
|
|
/// reference」)。配置树只存引用,永不存秘密明文;秘密值按 D5 由
|
|
/// CredentialStoring 写读。
|
|
struct CredentialReference: Hashable, Codable, Sendable {
|
|
let rawValue: String
|
|
|
|
init(rawValue: String) { self.rawValue = rawValue }
|
|
|
|
/// 新建凭据引用。
|
|
static func fresh() -> CredentialReference { CredentialReference(rawValue: UUID().uuidString) }
|
|
|
|
init(from decoder: Decoder) throws {
|
|
rawValue = try decoder.singleValueContainer().decode(String.self)
|
|
}
|
|
|
|
func encode(to encoder: Encoder) throws {
|
|
var container = encoder.singleValueContainer()
|
|
try container.encode(rawValue)
|
|
}
|
|
}
|