本文是 ns、NS App、NSD、NSGW 和管理控制台并行实现时必须共同遵守的协议基线。产品含义以产品闭环为权威,身份与注册分别以身份登录和注册协议为权威;管理对象状态/API 见控制资源生命周期,建流与传输见数据面协议,逐功能完备性见功能闭环总账。本文只固定跨进程、跨仓库和跨版本的可观察契约。
| 契约 | 权威生产者 | 消费者 | 禁止事项 |
|---|---|---|---|
| Identity / Session | Identity Authority + NSD | Client、管理 API | App 保存密码或自行判断 MFA 强度 |
| Device / Enrollment | NSD | Client、ns | 仅凭显示名认领 Device |
| Network / Membership | NSD | ns、Client | 客户端自行分配 Node IP |
| Grant / Policy Projection | NSD compiler | ns、Client | Dart、CLI 或 NSGW 重写授权算法 |
| Local Service Manifest | 节点所有者或受管授权 | ns、NSD | NSD 越过节点本地上限发布后端 |
| Service Directory / DNS | NSD projection | ns、Client | 读取失败渲染为空目录 |
| Route / Exit / Gateway Lease | NSD | ns、NSGW、Client | 候选列表替代数据面的最终校验 |
| Entitlement | Entitlement issuer → NSD resolver | NSD、NSGW | 客户端自报套餐能力 |
| Product Allocation / Relay Capacity | Commercial authority + Capacity Authority | provisioner、NSD、Platform Operations | NSGW 自报容量成为可售库存,或把价格/订单/Inventory 下发运行时 |
| Runtime Status | ns runtime | Client、CLI | Client 从日志文本猜运行状态 |
| Local Runtime Ownership | ns runtime + OS peer authentication | Client、CLI、installer/helper | 非 owner 读取租户状态,或 OS 管理员凭维护权限冒充 owner |
共享类型放在 ns-shared-next。共享仓库只承载稳定 wire schema、标识符、签名与错误类型,不承载数据库 ORM、产品 UI 或平台路由实现。
安全判定函数也属于共享契约权威。NSD、Web、ns、App 和契约测试不得自行重写 redirect 匹配、ID Token 验签/声明校验、Browser Cookie 属性或 Authorization Code 绑定判定;必须调用所固定 ns-shared-next revision 暴露的 matcher、verifier 和 authority validate 函数。各端 adapter 只能做传输字段转换与稳定错误映射;若共享函数无法表达新需求,必须先升版共享契约并统一更新消费 revision,不得在单个仓库增加本地例外。
所有外部对象使用不可枚举、全局唯一、类型明确的 ID。显示名可修改,绝不参与授权、幂等或引用完整性。
| 对象 | 建议字段 | 最小作用域 |
|---|---|---|
| Account | account_id | Identity Authority |
| Human/Machine Principal | principal_id | Organization |
| External Identity / Session | external_identity_id / session_id | Account + issuer |
| Organization | organization_id | Deployment |
| Network | network_id | Organization |
| Organization Membership / Group | org_membership_id / group_id | Organization |
| Admin Role Binding | role_binding_id | Organization + resource scope |
| Device | device_id | Organization;不跨组织复用 Device Key |
| Node Membership | membership_id | Network + Device |
| Service | service_id | Network |
| Endpoint | endpoint_id | Service + Membership |
| Grant Set / Rule | grant_set_id / rule_id | Network |
| Capability Policy / Activation | capability_policy_id / activation_id | Organization/Network |
| Route | route_id | Network |
| Gateway Instance / Pool | gateway_instance_id / gateway_pool_id | Deployment |
| Gateway Assignment | gateway_assignment_id | Organization + instance/pool target |
| Product Allocation | product_allocation_id | Organization + commercial authority |
| Relay Capacity Inventory / Reservation | relay_capacity_inventory_id / relay_capacity_reservation_id | Deployment + region + GatewayPool + capacity class / ProductAllocation |
| Gateway Profile | profile_ref | Network + Gateway Assignment |
| Gateway Enrollment Request / Key | gateway_enrollment_request_id / gateway_enrollment_key_id | Deployment |
| Gateway Pairing Session / Candidate | gateway_pairing_session_id / gateway_pairing_candidate_id | Deployment + pairing authority scope |
| Public Application / Edge Policy / Binding | application_id / edge_policy_id / binding_lease_id | Organization/Network |
| Share Offer / Binding / Federation | share_id / share_binding_id / federation_binding_id | provider/consumer Organization |
| Machine Credential | oauth_client_id / credential_id | MachinePrincipal |
| Posture Provider / Claim | posture_provider_id / posture_claim_id | Organization/Device |
| Rotation / Reservation | rotation_id / reservation_id | target object / Entitlement subject |
| Request / Operation | request_id / operation_id | Deployment |
每个 API、事件和审计记录都携带其权威作用域与目标 ID:Deployment 对象至少带 deployment_id,Organization/Network 对象再带必要的 organization_id、network_id。仓储层必须先按权威作用域过滤,再按对象 ID 查询;不能先查全局 ID 后在 handler 里补权限判断,也不能给 Deployment 级 GatewayInstance 伪造一个租户作用域。
所有会改变状态的 API 使用统一命令语义:
request_id 用于端到端诊断,不作为幂等键;idempotency_key 在同一命令与权威作用域内唯一,重复请求必须返回同一业务结果;expected_revision,版本不符返回 revision_conflict 并附当前 revision;impact_digest,提交时重新计算,不一致返回 impact_changed;committed_revision 和投影跟踪 ID。命令名称进入共享 registry,首版至少覆盖:identity/session、Organization/Network/Group/Role、Enrollment/Device/Membership、AccessGrant、CapabilityPolicy/Activation、Service/Publication、Route、GatewayInstance/Pool/Assignment/Selection/PublicApplication、GatewayPairingSession/Candidate、ProductAllocation、RelayCapacityInventory/Reservation、Share/Federation、MachinePrincipal/OAuth client、Posture、Entitlement/Reservation、Audit/Export/Delete/Hold。GatewayInstance/Pool 与 RelayCapacityInventory 使用 Deployment Operations authority,ProductAllocation 使用商业权威,Capacity Reservation 只能由商业 provisioner 在容量事务中写入,Assignment 使用 Organization authority,Activation/Selection 使用 Organization/Network authority;这些对象不能落进一个泛化的 Gateway update。Gateway pairing 使用 gateway.pairing.open|announce|confirm|reject|close,其中 announce 只能提交候选,不能创建 Instance、Assignment、Activation 或 Lease。每项 create/update/approve/suspend/revoke/rotate/delete 使用独立 action;完整状态和 API 以控制资源生命周期及专项文档为准。
后续版本已经保留但未进入首版发布范围的工作流,不得提前加入 v1 command/ID/error registry、OpenAPI、Client exhaustive enum 或发布门。当前范围以发布范围基线为准;启用保留能力必须先在共享仓库新增明确版本的契约,再统一升级所有生产者和消费者。
每种事件独立版本,不存在一个可以推断全部能力的“产品版本”。
事件目标使用 device、membership、gateway_instance 或 client_session 类型化引用,必须与认证身份和 payload 作用域一致。租户面对的 gateway_assignment_id 不能充当 GatewayInstance 的控制 target。客户端处理顺序固定为:验证版本可理解 → 验签 → 校验目标与作用域 → 校验 epoch/revision/有效期 → 解析 payload → 构建候选状态 → 原子替换 → 回执。任何一步失败都不能覆盖最后有效状态。具体 stream 与 payload 见Runtime Protocol v1。
每个投影流独立维护 (deployment_id, target, event_type, epoch, revision) 水位。本地 profile_id 只用于 Client 选择保存的控制端,不进入 NSD 签名事件:
base_revision 必须等于本地 revision;projection_outdated,状态为 outdated_applied;事件回执区分 received、applied、rejected。applied 必须包含实际生效 revision;rejected 包含稳定错误码和可重试性,但不得回传私钥或完整敏感 payload。
跨层功能状态不得压成布尔值。所有功能至少分成四个正交轴:
| 轴 | 回答的问题 | 示例 |
|---|---|---|
| intent | 用户/管理员想要什么 | enabled / disabled / required |
| authority | 权威是否允许 | authorized / denied / unknown |
| projection | 控制面是否给出可执行配置 | waiting / matched / mismatch / expired |
| runtime | 本机或网关是否实际生效 | starting / ready / degraded / blocked / failed |
Client 展示由这些结构化轴推导,不得用“连接成功”覆盖部分失败,也不得用“无资源”覆盖读取故障。
错误码按原因而不是页面命名。固定分类如下:
| 类别 | 示例 | 默认动作 |
|---|---|---|
| authentication | session_expired | 重新登录,不撤 Membership |
| authorization | not_authorized | 不重试,联系管理员或修改 Grant |
| admission | quota_limit_reached | 显示真实资源与限制 |
| conflict | revision_conflict, route_conflict | 刷新权威状态并让用户决定 |
| unavailable | candidates_not_ready | 保持意图,等待或重试 |
| read_failure | projection_read_failed | 保留缓存,显示故障 |
| incompatible | schema_version_unsupported | 更新对应组件 |
| local_policy | local_manifest_denied | 在节点本地处理 |
| safety | capture_blocked, signature_invalid | 保持 fail-closed,禁止自动降级 |
| local_ownership | runtime_owned_by_another_local_user, runtime_owner_inactive | 不泄露 owner/tenant;等待、释放或明确 reset |
错误结构允许新增可选 details 和 remediation,但不能改变既有 code 的含义。remediation 是服务端根据当前对象、actor、allowed actions 和产品能力返回的结构化下一步;Client 只能渲染,不能按 error code 自行推断。
v1 remediation 是 schema_version=1 的闭合 tagged union,kind 仅允许:
| kind | 允许的最小 payload | Client 动作 |
|---|---|---|
retry | retry_after_ms? | 重试同一 operation,不改变 payload |
reauthenticate | required_assurance? | 重新登录或 step-up |
contact_owner | owner_display_name?, owner_contact_masked?, resource_ref? | 展示脱敏 owner 信息或复制联系摘要 |
open_product_view | view_id, resource_ref? | 打开 Client 内置、版本识别的产品视图 |
open_system_settings | settings_kind | 调用平台受支持的系统设置入口 |
update_component | component, minimum_version? | 进入签名更新流程 |
use_allowed_scope | allowed_scope | 把服务端返回的规范范围作为明确的新草稿,用户仍需再次保存 |
view_billing | quota_ref? | 打开内置 Usage & Billing 视图 |
copy_support_info | request_id, diagnostic_ref? | 复制脱敏诊断信息 |
view_id、settings_kind、component、resource_ref.kind 和 allowed_scope.kind 都是共享 registry 的闭合枚举;payload 不允许携带任意 URL、任意相对路径、shell command 或可直接执行的客户端动作。owner 信息只在当前 actor 被允许披露时返回,并保持脱敏。首版不包含 request_access。未来增加自助申请必须发布新的共享 schema revision,不能让旧 Client 把 not_authorized 自动解释成“可申请”。用户文案由 Client 本地化,服务端 message 仅用于诊断。
能力发现分两层:
/.well-known/nsio-capabilities 用于匿名预检,短缓存、带 ETag,不作为安全权威;Manifest 只能关闭双方不共同支持的可选功能,不能降低签名、Device Key 绑定、确认页防钓鱼、租户隔离、授权或 fail-closed 基线。客户端自报“不支持安全能力”时,服务端拒绝对应流程,不切到更宽松路径。
首版共享 credential registry 至少包含:EnrollmentGrant、GatewayEnrollmentGrant、control credential、PeerPathLease、RelayRouteLease、RelayBudgetLease、ServiceFlowCredential、RouteAccessCredential、Capability Lease、EgressOriginLease、ExitTunnelLease、IngressBackendCredential、FederatedAccessLease、OAuth Access Token、EdgeSessionCredential 和 signed EdgeIdentityContext。Capability Lease 必须显式绑定 activation_id、gateway_instance_id 和 generation;不能使用含义不明的通用 gateway_id。RelayBudgetLease 只分配 opaque budget subject 的本地 QoS 份额,不能授权 route、peer、tenant capability 或数据面资源;过期回落 fallback profile,不复用 Capability/Route Lease 的关闭语义。每类 credential 有独立 audience/claims/schema/TTL/revoke key,不能用一个通用 bearer token 跨数据面。
浏览器配对用于托管控制下少量、有人值守的客户自建 NSGW。管理员必须先在已认证控制台打开一个短期、有界的 GatewayPairingSession,再允许未注册进程提交候选;不存在可被任意匿名进程填充的全局 pending 列表。gateway_pairing_session_id 是至少 128 bit 随机相关句柄,只允许向该 Session 提交候选,既不是 bearer Enrollment Key,也不能批准、兑换或创建任何正式 Gateway 对象。窗口外的 announce 不创建产品/业务对象,但仍进入匿名安全计数、限速和告警。
权威 transcript 使用闭合、版本化的类型,而不是字符串拼接:
authority_scope 是闭合 tagged union,必须包含 deployment_id,客户自建轨还必须包含 organization_id。transcript 必须复用共享仓库的 production canonical serializer 与 domain-separated digest 路径(与 reflexive_value_digest<T: Serialize> 同一规范),禁止裸字段串联、手写 JSON、依赖 map 顺序或使用显示字符串。NSGW 用 Gateway 私钥签署 exact canonical transcript;NSD 在创建候选前验证 Session、challenge、scope、签名、ceiling digest、TTL 和 candidate limit。
双方显示的四词比较短语是版本化固定词表对 domain-separated canonical transcript digest 的确定性映射。词表版本、digest-to-index 算法、Unicode/ASCII 展示和 golden fixtures 由 ns-shared-next 的单一 helper 持有,NSD、NSGW 和 Web 不得各自实现。它同时绑定 Deployment、Organization、Session、challenge、Gateway Key 和待冻结的 capability ceiling;不能只从公钥、随机短语或 Session ID 生成。短语仅帮助人确认两个界面表示同一 transcript,不替代管理员身份、step-up 或服务端签名校验。
确认命令必须原子完成:重新验证 Session/revision/candidate/transcript → step-up 与管理权限 → 单次消费 Session → 创建 GatewayInstance → 创建或加入同 ownership 的 GatewayPool → 创建/复用 customer_owned GatewayAssignment → 保存不可变 enrollment provenance → 写审计/outbox。任一步失败不留下部分正式对象;Session 已消费后任何 candidate 都不能再次确认。不同 Organization/ownership 的实例不能进入同一客户池。拒绝只关闭候选并写安全事件,不生成正式对象。
浏览器配对与 Gateway Enrollment Key/Workload Identity 最终写入同一份不可变 provenance:enrollment_method、proof_source_id、proof_source_revision、accepted_at、enrollment_capability_ceiling 及其 digest。配对轨的 ceiling 就是管理员确认 transcript 中的 enrollment_capability_ceiling_digest 对应值;Key 轨来自被接受的 Key revision;attestation 轨来自被批准的 workload policy revision。注册后不得重算、放宽或改写这份历史快照。
Enrollment Grant、Session、控制事件和短 Lease 继续由服务端时间判定,客户端倒计时只用于提示。P1 的限时 AccessGrant 使用绝对 UTC 毫秒,不使用“收到后再存活 N 分钟”的相对 TTL:
整个 validity 缺省时不序列化;对象存在时至少有一个边界,两个边界同时存在时必须满足 not_before_ms < not_after_ms。P0 永久 Grant 不携带该字段,因此不改变现有 canonical digest。
共享时间常量固定为:
执行端在验签、作用域、epoch/revision 和 payload digest 全部通过后,使用签名 envelope 的服务端时间建立 TimeAnchor(server_time_ms, suspend_inclusive_elapsed_ms, event_id, revision)。后续可信时间只按“服务端绝对时间 + 包含系统睡眠的 elapsed”推进;不得使用会在 suspend/hibernate 期间停止的普通进程计时器,也不得因系统 UTC 回拨延长授权。
首次锚点与本地 UTC 的差异不得超过 TIME_ANCHOR_INITIAL_SKEW_MS_V1;刷新锚点与上一可信时间估值的差异不得超过 TIME_ANCHOR_REFRESH_SKEW_MS_V1。超过边界、平台不能提供包含睡眠的 elapsed、进程重启后尚未取得新锚点,或时间值越界/不可表示时,带 validity 的 Grant fail-closed,永久 Grant 不受影响。唤醒并恢复控制连接时刷新锚点;从缓存重载投影不得重置绝对 not_after_ms 或重新获得完整 TTL。
可信 now < not_before_ms 时尚未生效,now >= not_after_ms 时立即拒绝。NSD 同时保留定时重编译、删除投影、管理端展示和撤销推送,但不是到期的唯一执行者。
time_bound_grant_v1 按每条 Grant 的实际执行端集合求能力交集:L3 为源/目标节点,Service 为 consumer/publisher(公网路径再包含 NSGW),Route/Exit 为相关客户端与 Gateway/Connector。创建或扩大 Grant 时必须先校验当前执行端,不支持时拒绝保存并列出组件及“改为永久授权/升级组件”动作,不能先返回成功再在投影阶段静默丢弃。之后 selector 新增的不兼容执行端只对该端 fail-closed、不获得该 Grant;其他兼容执行端继续生效,不能为一个旧节点撤销整条 Grant。管理端显示已执行数量、未执行端和升级入口。
撤销不依赖“立即收到一条删除事件”这一单点。Peer、Route、Gateway 和跨组织分享都使用短租约,使丢失撤销事件时也能在 SLA 内自然失效。
所有跨组件操作共享 request_id,关键状态变化另有稳定 operation_id。日志字段至少包含 component、deployment、organization、network、resource、revision、result 和 error code。指标只记录必要元数据,不记录业务内容。
必须可回答:
每个共享 schema 都必须具备:
共享契约未通过这些门,任何组件都不能宣布对应功能完成。