Skip to content

Invite Addressing and Principal Locator

This content is not available in your language yet.

Realm join locator 的闭包由 ak.vector.realm_join_candidate.untrusted_locator.v1 验证;Directory 不承载或返回 RealmJoinCandidate。

0. 规范语言

本文中的规范关键字(MUST / SHOULD / MAY 等)按 ../conformance/normative-language.md 解释;仅大写形式具规范约束力。

1. 模型

Realm invite 的基础寻址模型是:

invite_delivery = invite_address + introduction_evidence

邀请目标的规范输入是显式 invite_address:

{
"account_id": {
"principal_id": "ak:did_core:webvh:z2dmjBobExample",
"station_id": "ak:did_core:webvh:zGiUQcWG9yy3Z9pMs15w7JHgc"
},
"service_resolution": {
"resolution_url": "https://ps.bob.example/_arkret/open/services/ak%3Adid_core%3Awebvh%3AzGiUQcWG9yy3Z9pMs15w7JHgc/resolution"
}
}
字段类型必填说明
account_idAccountIdMUST被邀请 holder 的完整账号身份;principal_id 与 station_id 均不可省略或由上下文推断。
service_resolutionservice_resolution_carrierMUSTaccount_id.station_id 的首跳路由材料;形态必须是完整 method evidence 的 inline,或 resolution_url 发现线索。

account_id.station_id 在 v1 中只表示托管该账号的 Station。它 MUST NOT 指向 Realm governance Station、push gateway、Directory 或任意第三方服务,除非该服务恰好也是该账号的托管 Station。客户端 MUST 从显式 AccountId 取得目标 Station,MUST NOT 从当前 session、URL 或 DID Document 补齐账号身份。

invite/locator 必须携带 service_resolution 作为发现载体。可选 transport-only route_assistance.mirror_hints[] 最多四项,每项包含 mirror service 的 did_core_id 和独立 service_resolution_carrier。镜像只能转交目标 DID 方法证据,不能自行授权目标入口。该对象不改变 invite 的业务授权或 exact invitee AccountId,接收方 MAY 忽略。

使用 route_assistance 时仍必须执行 service-surface.md §2.6 的独立方法验证、新鲜度及网络安全规则。提示不授予权限,不得扩展为部署级镜像列表或披露完整成员列表;locator/token 到期不延长路由缓存,缓存也不延长 token。

2. Introduction Evidence

每个私有 invite delivery request MUST 携带 introduction_evidence,说明邀请方为什么可以尝试联系被邀请方。schema 见 invite-delivery-request.schema.json。same_station 不是可发送的 evidence 分支;它只能由接收端从已验身份派生。

kind信任强度说明
locator_ref高被邀请方主动生成 / 交付的在线 locator ref。默认推荐。
consent_grant高邀请者出示被邀请方主动签发给邀请者的 ak.consent.grant(scope invite 或 any)的 consent_grant_ref。信任来源与 locator_ref 同构:都是被邀请方主动交付给邀请者的授权材料。已是联系人(互授 invite consent)拉群走此 kind,无需 locator URL。
shared_realm高邀请者与被邀请者已经同在某个 Realm;接收方按本地 policy 判断该 Realm 是否可信。
handle_claim发现信任邀请者通过 verified handle claim 找到 exact account_id。它证明 holder 或受信 issuer 将某 handle 披露为可解析入口,但不证明 holder 已同意该邀请者联系自己。默认 SHOULD quarantine 或 drop;只有 subject policy 与部署约束都允许时才可 notify。
same_station中 / 部署相关receiver-only 分类:已验 invite Event 的 actor_id 必须是 account Actor,且其 account_id.station_id 与 invite_address.account_id.station_id 逐字相等。sender 不携带此 evidence。
explicit_address弱邀请者只提供 account_id + service_resolution;等价于“我知道或猜测这个地址及其可验证首跳材料”。默认 SHOULD quarantine 或 drop。

explicit_address 是合法但最低信任 evidence。接收方 MUST NOT 因为请求格式正确就通知用户;必须先应用 subject 私有 invite_receive_policy。

引入信任分档(normative):高信任档 = {locator_ref, consent_grant, shared_realm};发现信任档 = {handle_claim};低信任档 = {same_station, explicit_address, 无 / 非法 evidence}。该分档同时决定 §5 的分级披露行为。

consent_grant evidence 的接收方验证:consent_grant_ref 指向的 ak.consent.grant 在被邀请方(invite_address.account_id)的 consent typed current result 中仍是 active grant dot,且 peer == inviter、consent_scope ∈ {invite, any}、未过期未撤销。验证通过即按高信任处理。consent_grant_ref 校验失败时,接收方 MUST 降级按 explicit_address(低信任)处理,MUST NOT 因为携带了 evidence 字段就放行。

InviteAddress 唯一收件身份是 account_id。handle_claim evidence 的接收方验证:handle_claim.claim.handle == evidence.handle,handle_claim.claim.subject_account_id MUST 精确等于 invite_address.account_id,handle_claim.status=verified,as_of < fresh_until 且当前时刻仍在该 signed freshness window 内,claim.expires_at 未过期,claim.proofs[0..1] 与顶层 evidence 均有效,revocation 为 null,且 claim.issuer_id / Directory / claim.visibility / claim.audience 满足该账号的 policy 与部署约束。任何校验失败 MUST 降级按低信任 explicit address 处理;不得从 handle、DID Document 或当前服务补齐 AccountId 分量。

3. 在线 Principal Locator

二维码 / 链接默认承载在线 locator ref,不承载完整 signed locator。

3.1 认证发行、轮换与撤销

locator 只能由 subject 当前 Station 的认证 self surface 管理;客户端不得自行铸造 token。v1 定义:

operationHTTP binding语义
ak.self.invite_locator.command.issue.v1POST /_arkret/self/invite-locators为 session actor 发行新 locator。body 可含 ttl_seconds(默认 900,范围 60..3600)、one_time_use(默认 false)与可选 display_hint。account_id 与当前 service_resolution 均由服务端从认证 session、本机 service identity 和已验证 route record 推导,MUST NOT 由客户端提交。
ak.self.invite_locator.command.rotate.v1POST /_arkret/self/invite-locators/rotate在同一 durable transaction 中撤销 locator_id 指向的旧 locator 并返回全新 locator/token。旧 locator 不存在、已撤销、已消费或不属于 session actor 时 MUST 返回 not_found,不得替调用方泄露归属或状态。
ak.self.invite_locator.command.revoke.v1POST /_arkret/self/invite-locators/revoke撤销属于 session actor 的 locator;对同一已撤销 locator 的重复请求是 idempotent success。不存在、不属于 actor 或已消费的 locator 返回 not_found。

issue / rotate 的成功响应是 principal-locator.schema.json#/$defs/invite_locator_issue_outcome,其中 locator_token 是以 CSPRNG 生成、至少含 192 bit 熵且只返回一次的 bearer secret;响应 MUST 携带 Cache-Control: private, no-store,服务端 MUST NOT 持久化 raw token,任何中间层也不得缓存响应体。revoke 成功响应是 #/$defs/invite_locator_revoke_outcome;同一 AccountId 对已撤销 locator 的重试 MUST 返回首次撤销记录的原始 revoked_at,不得用重试时刻改写它。这些 self operation 使用普通 session + PoP 写认证;locator 归属绑定 principal account,而不是某个 device/session,因此同一 AccountId 的其它有效 session MAY 轮换或撤销它。

服务端 durable locator record MUST 至少保存:locator_id、token_digest(唯一索引)、account_id、service_resolution、issued_at、expires_at、one_time_use、display_hint?、revoked_at?、consumed_at?。token_digest MUST 使用 sha256:<lowercase_hex>,raw token MUST NOT 出现在数据库、audit log、analytics、crash report 或 durable event。resolve 成功时,返回的签名 principal_locator.locator_ref_digest MUST 精确等于该 record 的 token_digest,且 account_id、service_resolution、有效期与 display_hint? 必须从同一 record 派生,不得信任 resolve 调用方输入这些字段。每个 exact AccountId 同时 active locator 的 v1 上限为 16;达到上限时 issue MUST fail closed(rate_limited 或 failed_precondition),不得隐式撤销调用方未指定的 locator。

rotate 必须是“发行新 token + 原子撤销旧 token”,不另设 refresh alias;客户端点击刷新时调用 rotate,并用返回的新 token 替换进程内显示值。rotate 省略 ttl_seconds 时 MUST 保留旧 record 的已授予 lifetime(expires_at - issued_at),省略 one_time_use 时 MUST 保留旧值,省略 display_hint 时 MUST 保留旧 hint;仅显式 display_hint:null 清除 hint,避免普通刷新静默扩大可用性或丢失展示信息。若 rotate 的 transport outcome 不确定,客户端 MUST NOT 直接调用 issue:它必须先对旧 locator_id 调用幂等 revoke,取得成功或可确认的终态,使旧 token 确定失效,再以 fresh request 调用 issue;首次 rotate 若已提交但响应丢失,其不可恢复的新 token 只作为短 TTL orphan 等待过期。one_time_use=true 时,resolve 在读取 record、检查 TTL/撤销/策略并准备成功响应的同一原子操作中写入 consumed_at;并发 resolve 最多一个成功。消费是 resolve 的内部状态迁移,不定义独立公开 consume operation。

3.2 OOB handoff 与 resolve

推荐 QR / link 文本:

https://ps.bob.example/_arkret/open/invite-locators/resolve#token=<locator_token>

扫描方客户端读取 fragment 后,向同一 origin 提交:

POST /_arkret/open/invite-locators/resolve

body:

{
"locator_token": "base64url-token-with-at-least-128-bit-entropy"
}

locator_token MUST 只通过 JSON body 或等价 signed proof 提交,MUST NOT 出现在 URL path、query string、Referer、普通 access log、analytics、crash report、local storage 或浏览器历史中。客户端读取 fragment 后 MUST 清理地址栏与本地临时状态。

token 要求:

  • locator_token SHOULD 是不透明 server-side handle;服务端私有状态保存 account_id、service_resolution、TTL、撤销状态与接收策略。
  • locator_token MUST 至少 192 bit 熵(与 §3.1 签发要求同一数值,不存在更低的验收下界);base64url 无 padding 编码时 192 bit 为 32 字符。
  • locator_token MUST NOT 是明文可解码的 base64url(JSON),也不得在 token 明文中携带 account_id、expires_at、策略状态或其它可识别 invitee 的材料。若部署需要 stateless token,payload MUST 先做 authenticated encryption;调用方仍只把它当 opaque bearer secret。
  • token MUST be unguessable、可撤销、可设置短 TTL,并 MAY 设置一次性使用。
  • endpoint 对不存在、过期、撤销、策略拒绝的对外响应 MUST byte-identical 或等价不可区分(含 status / body / headers);timing 侧信道按 conformance/conformance-vectors.md ak.vector.invite.failure_indistinguishable.v1(§9.7)收口(timing 差异 SHOULD ≤ 50ms,高安全 profile MUST 用 jitter / padding)。仅服务端 audit log MAY 记录具体 reason_code。
  • endpoint 返回体 MUST 是签名 principal_locator;调用方不能只信任 HTTPS URL。

4. principal_locator

principal_locator 是被邀请方 Station 返回的、可验证的 invite address assertion。schema id 为 ak.schema.principal_locator.v1。

最小形态:

{
"schema": "ak.schema.principal_locator.v1",
"account_id": {
"principal_id": "ak:did_core:webvh:z2dmjBobExample",
"station_id": "ak:did_core:webvh:zGiUQcWG9yy3Z9pMs15w7JHgc"
},
"service_resolution": {
"resolution_url": "https://ps.bob.example/_arkret/open/services/ak%3Adid_core%3Awebvh%3AzGiUQcWG9yy3Z9pMs15w7JHgc/resolution"
},
"issued_at": "2026-06-07T10:00:00Z",
"expires_at": "2026-06-07T10:15:00Z",
"locator_ref_digest": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"proofs": [
{
"proof_purpose": "recipient_service_acceptance",
"proof": {
"kind": "detached_jws",
"verification_method": "did:webvh:zGiUQcWG9yy3Z9pMs15w7JHgc:ps.bob.example#server-key-1",
"payload_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"created_at": "2026-06-07T10:00:00Z",
"jws": "..."
}
}
]
}

验证规则:

  1. account_id MUST 是完整 closed AccountId;service_resolution MUST 对应 account_id.station_id。
  2. recipient-service proof 的已验证 signer service DID MUST 等于 account_id.station_id;holder proof 若出现,其已验证 principal core MUST 等于 account_id.principal_id,且签名覆盖完整 AccountId。
  3. locator_ref_digest 绑定私有 locator ref material;raw token 不得写入 Realm durable event。
  4. proof.payload_digest MUST 覆盖 canonical_json(principal_locator_without_proofs)。
  5. proofs[] MUST 至少包含 recipient_service_acceptance;高安全 / audited / enterprise 部署 SHOULD 同时要求 subject_locator_authorization。
  6. verifier MUST 验证 service_resolution 得到当前方法验证后的 AuthenticatedServiceResolution,确认 service_id == account_id.station_id、project(normalized_did_document.did) == account_id.station_id、freshness 与 endpoint binding。Station MUST 仅为经认证 self operation 选定的同一 AccountId 签发 locator;holder proof 的附加要求仍按第 5 步执行,不得用独立 delivery-binding 对象补齐 AccountId。

principal_locator 不是 membership grant,也不是 invite accept proof。它只证明该 exact AccountId 与 Station service resolution 的寻址关系。

5. 接收策略

被邀请方 Station 按 subject 私有 invite_receive_policy 决定哪些 evidence 可以通知用户。schema id 为 ak.schema.invite_receive_policy.v1。

{
"schema": "ak.schema.invite_receive_policy.v1",
"account_id": {
"principal_id": "ak:did_core:webvh:z2dmjBobExample",
"station_id": "ak:did_core:webvh:zGiUQcWG9yy3Z9pMs15w7JHgc"
},
"holder_allowed_introduction_kinds": [
"locator_ref",
"consent_grant",
"shared_realm",
"handle_claim",
"same_station"
],
"handle_claim_behavior": "quarantine",
"explicit_address_behavior": "quarantine",
"unknown_invites": "drop",
"consent_profile": "default",
"new_source_quota": {
"new_sources_per_window": 2,
"new_sources_per_retention": 20
},
"allowed_handle_domains": [
"bob.example"
],
"trusted_handle_issuer_ids": [],
"trusted_directory_ids": [],
"trusted_realm_ids": [],
"trusted_source_ids": [],
"denied_source_ids": [],
"denied_actor_ids": [],
"disclosure": {
"high_trust": "outcome",
"discovery_trust": "opaque",
"low_trust": "opaque"
}
}

规则:

  • 缺省 policy(subject 未发布 invite_receive_policy 时)MUST fail closed(normative):接收方 MUST 采用保守默认——holder_allowed_introduction_kinds 仅含高信任档 {locator_ref, consent_grant, shared_realm};发现信任档 {handle_claim} 与低信任档 {same_station, explicit_address} 默认 quarantine(SHOULD)或 drop,MUST NOT 仅凭 evidence 格式正确就触发用户通知。上方示例把 handle_claim 与 same_station 列入 allowlist,是 subject / 部署的显式可达性配置,不是协议默认;same_station 与 explicit_address 同属低信任档(§2),默认处置对称——不得让”同一 Station 上的任意账户”仅凭同域承载即向同域任意 subject 发起会触发通知的邀请(同域无授权骚扰开口)。
  • holder_allowed_introduction_kinds 是 allowlist;未列出的 evidence MUST NOT 触发用户通知。consent_grant 是受推荐的高信任 kind:把它加入 allowlist 即允许”已互授 invite consent 的联系人”直接邀请,而无需 locator URL。
  • handle_claim_behavior 取值为 drop | quarantine | notify;省略时 MUST 视为 quarantine。把 handle_claim 配为 notify 表示 holder 显式希望别人可通过已发布 handle 发起邀请通知;该选择仍受 §5.2 部署约束限制。实现 MUST NOT 把任何 connection identifier、未 verified handle、过期/revoked handle claim 或未授权 restricted handle 当成 handle_claim evidence。
  • explicit_address_behavior 取值为 drop | quarantine | notify;默认 SHOULD 是 quarantine 或 drop。把低信任档 evidence(same_station / explicit_address)配为 notify 是部署对该档的显式放宽,MUST 经部署有意配置,不得作为缺省。
  • denied_handle_domains 先于 allowlist 生效,命中时 MUST 按策略拒绝处理。allowed_handle_domains 若非空,handle_claim.claim.handle 的 domain MUST 是列表中的 canonical IDNA A-label 精确域名;子域名不自动继承,必须显式列出。trusted_handle_issuer_ids / trusted_directory_ids 若非空,handle_claim.claim.issuer_id 或 resolved_by MUST 命中对应 allowlist。
  • unknown_invites 取值为 drop | quarantine;无 evidence 或不合规 evidence 不得默认 notify。
  • consent_profile 取值为 default | require_explicit_consent;省略时 MUST 视为 default。它是 holder 对 ../identity/consent-model.md §6.1 invite consent gate 强度的唯一 carrier:require_explicit_consent 下只有第 7 步已验证的 consent_grant evidence 可以 notify,其余 evidence(含高信任档 locator_ref / shared_realm、handle_claim、same_station、explicit_address 与无 evidence)一律静默 drop——不 quarantine、不计 new_source_quota、不写任何 holder-private typed current result,且 §5.1 披露强制为 opaque(status="deferred"、无 disclosed_outcome),不受 disclosure / disclosure_max 影响。该字段是 subject 私有 state:不进入 ServiceDescribe,requester 与 peer Station MUST NOT 能观察;§5.2 部署约束没有对应字段——部署只能经行为上限与 disclosure_max 收紧,不能替 holder 选择 profile。它与 Realm 级 preauth.consent_required 相互独立。
  • new_source_quota 是 holder 对”此前未见过的新来源 peer”进入 quarantine inbox 的私有上限,两个成员均为 integer ≥ 0,0 表示锁死新来源。它只能把 holder 变得更不可达:effective 值取subject 值与 §5.2 部署 max_* 中更小者,省略成员取部署 default_*。判定算法、identity key 与ledger 语义由 ../identity/consent-model.md §6.1.1 唯一定义;本字段只是它的 holder 侧 carrier,超限后的处置仍是同一 opaque deferred。
  • denied_actor_ids 是按 exact peer ActorId 的黑名单;inviter 命中时,delivery MUST drop,且 §5.1 披露 MUST 强制为 opaque,以免黑名单经回包侧信道泄露。同 principal core 异 Station 的 account Actor 不得互相命中。trusted_source_ids / denied_source_ids 是另一独立维度,只匹配已认证 transport source service DID,不代表 inviter,也不得替代 denied_actor_ids。
  • policy 是 exact AccountId 的私有 state,不得写入目标 Realm event log。policy.account_id MUST 等于认证 holder 的完整 AccountId;同 principal 在另一 Station 的 policy、consent、quarantine、设备与通知不得继承或合并。

5.1 分级披露(graded disclosure,normative)

invite_receive_policy.disclosure 决定 delivery outcome 回送给邀请者的结果粒度,按 §2 引入信任分档区分:

  • disclosure.high_trust(默认 outcome):作用于高信任档 {locator_ref, consent_grant, shared_realm}。
  • disclosure.discovery_trust(默认 opaque):作用于发现信任档 {handle_claim}。
  • disclosure.low_trust(默认 opaque):作用于低信任档 {same_station, explicit_address, 无 / 非法 evidence}。

disclosure_level 语义:

  • opaque:invite_delivery_outcome 只返回 generic status(accepted | duplicate | deferred),MUST NOT 携带 disclosed_outcome,且对 exists / not-exists / quarantine / drop 各情形不可区分。这是反枚举 / 反侧信道的默认。
  • outcome:invite_delivery_outcome MAY 携带 disclosed_outcome,把真实处理结果告知邀请者。disclosed_outcome 的封闭枚举只有 delivered | blocked 两值。

quarantine MUST NOT 被回送(normative):disclosed_outcome 的上界由 ../identity/consent-model.md §6.1.1 的不可区分 MUST NOT 决定。delivered 与 blocked 回答的是“这次投递是否被接收方策略放行”,属于本节设计意图内的反馈;而“invite 进入 holder quarantine inbox”回答的是 holder 的 consent 决策尚未作出——那是 consent-model §6.1 明确的 holder-private 状态,等价于回答“holder 未对该 requester 授予 active invite grant”。高信任档只说明 inviter 已知 holder 存在,泄露的不是 existence 而是 consent 状态,因此不构成可以回送的理由。

quarantine 的 wire 落点固定为 status="deferred" 且不携带 disclosed_outcome:deferred 与“正在重试投递”、“holder 侧尚未处理”共用同一语义,因而不构成对 quarantine 的可区分指示。该映射在所有信任档、所有 disclosure 取值下一致,不因 high_trust=outcome 而改变。

设计意图:对已建立信任的来源(已互授 invite consent 的联系人、对方主动给的 locator、已同在 Realm),邀请被接收方策略拒绝时能给邀请者明确反馈,避免”联系人加不进却不知为何”的 UX 黑洞;对可发现但未建立关系的来源(handle_claim)与陌生人(explicit_address)默认保持不可区分。denied_actor_ids 命中者无论 disclosure 设置一律 opaque。disclosure 整体省略时按默认 high_trust=outcome / discovery_trust=opaque / low_trust=opaque。

5.2 部署接收约束(normative)

Station MAY 发布部署 / 管理员级 receive_policy_constraints。该对象是 subject 私有 invite_receive_policy 的上限,不是默认放宽项。有效接收策略按交集计算:

effective_receive_policy =
subject invite_receive_policy
∩ Station receive_policy_constraints
∩ applicable organization / realm policy constraints

约束规则:

  • 部署约束只能让 subject 更不容易被联系,MUST NOT 把 subject 从更隐私的设置强制放宽为可通知。若 subject 选择 drop,管理员不能通过约束把结果提升为 quarantine 或 notify。
  • deployment_allowed_introduction_kinds 与 deployment_denied_introduction_kinds 先于 subject allowlist 生效;任一约束拒绝的 evidence kind MUST 按 drop 或 indistinguishable policy denial 处理。
  • 行为强度排序为 drop < quarantine < notify。handle_claim_max_behavior、explicit_address_max_behavior 与 unknown_invites_max_behavior 是上限;effective behavior 取 subject 行为与上限中更严格者。
  • disclosure_max 是部署 / 管理员对 §5.1 分级披露粒度的上限,按 §2 引入信任分档给出 {high_trust_max, discovery_trust_max, low_trust_max},取值同 disclosure_level 枚举(opaque < outcome,opaque 更保守)。字段或某档省略表示该档不设部署级披露上限。effective disclosure 取 subject invite_receive_policy.disclosure 与 disclosure_max 中更保守(更接近 opaque)者,使部署可以把 subject 自愿设为 outcome 的披露强制收紧为 opaque(反枚举 / 反侧信道),但 MUST NOT 把 subject 设为 opaque 的披露放宽为 outcome。该交集与上面的行为交集独立计算:先按行为上限定 drop / quarantine / notify,再按 disclosure_max 定 outcome 是否可回送。denied_actor_ids / denied_source_ids 命中时仍无条件强制 opaque,不受 disclosure_max 影响。
  • allowed_handle_domains、trusted_handle_issuer_ids、trusted_directory_ids、trusted_source_ids、accepted_subject_did_methods 是部署级 allowlist;字段省略表示该维度不设部署级上限,字段存在且为空数组表示不接受该维度的任何候选。非空时必须命中。未命中 MUST 视为策略拒绝,不得通过响应区分“存在但被策略拒绝”和“不存在”。
  • denied_source_ids 命中时 MUST drop 且强制 opaque。
  • applies_to 是本对象筛选面的封闭列举,取值 invite_delivery | contact_request,省略等于两条全选。它只筛选 introduction-evidence 与分级披露类成员——deployment_allowed_introduction_kinds、deployment_denied_introduction_kinds、三个 *_max_behavior、disclosure_max,以及 handle domain / handle issuer / directory / source / DID method 各表。new_source_quota 不受 applies_to 筛选,见下一条。../identity/consent-model.md §6.1.1 的 first-contact admission 面不携带 introduction evidence、也不参与 §5.1 分级披露,上述成员在该面上没有可筛选的对象,因此本枚举不为它新增取值。
  • new_source_quota 是 ../identity/consent-model.md §6.1.1 per-holder 新来源限速的唯一部署 carrier。字段与缺省:window_seconds(86400)、default_new_sources_per_window(3)、max_new_sources_per_window(10)、retention_seconds(2592000)、default_new_sources_per_retention(30)、max_new_sources_per_retention(200)。省略该对象或任一字段不等于关闭 quota,缺省即上表值;MUST 不变式 max_* ≥ default_* 与 retention_seconds ≥ window_seconds 由 validator 强制,违反者整个 constraints 对象以 schema_violation 拒绝。该 quota 与本节其它上限的交集独立计算:先按行为上限定 drop / quarantine / notify,命中 quarantine 后才在 admission chokepoint 执行 quota 判定。该对象 MUST NOT 被 applies_to 筛选(normative):它是 ../identity/consent-model.md §6.1.1.3 holder admission chokepoint 的阈值,invite delivery、contact delivery 与 consent request 三条面共用同一份 ledger 与同一组阈值,因此 applies_to 取何值都不改变它对三条面无条件生效。

示例:

{
"policy_version": "2026-06-21",
"applies_to": ["invite_delivery", "contact_request"],
"deployment_allowed_introduction_kinds": [
"locator_ref",
"consent_grant",
"shared_realm",
"handle_claim"
],
"handle_claim_max_behavior": "quarantine",
"explicit_address_max_behavior": "drop",
"new_source_quota": {
"window_seconds": 86400,
"default_new_sources_per_window": 3,
"max_new_sources_per_window": 10,
"retention_seconds": 2592000,
"default_new_sources_per_retention": 30,
"max_new_sources_per_retention": 200
},
"allowed_handle_domains": ["acme.example"],
"trusted_handle_issuer_ids": ["ak:did_core:webvh:z43vHHHeh32Hnyv6t7X3t33Xs"],
"trusted_directory_ids": ["ak:did_core:webvh:z43vHHHeh32Hnyv6t7X3t33Xs"],
"accepted_subject_did_methods": ["did:webvh"]
}

6. Durable Event Boundary

ak.invite.create 是 Realm durable Event。genesis payload MUST 只携 exact invitee_account_id、introduction_evidence_digest 与 expires_at;Invite ID 从 Event ID 派生,MUST NOT 在 genesis payload 重复携带。 路由材料、locator token、raw introduction_evidence 与 invite_receive_policy MUST NOT 进入 durable payload。

{
"invitee_account_id": {
"principal_id": "ak:did_core:webvh:z2dmjBobExample",
"station_id": "ak:did_core:webvh:zGiUQcWG9yy3Z9pMs15w7JHgc"
},
"introduction_evidence_digest": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"expires_at": "2026-06-14T10:00:00Z"
}

规则:

  • payload.invitee_account_id MUST 与 invite_address.account_id 完整相等,不得只比较 principal。
  • destination service 只从 invitee_account_id.station_id 派生;service resolution 与 route assistance 只在私有 transport carrier 中出现,MUST NOT 要求 durable Event 镜像它们。
  • introduction_evidence_digest = digest(canonical_json(private_delivery_introduction_evidence)),用于审计关联,不得泄露 raw locator token。
  • 普通定向邀请的取消 / 拒绝 MUST 使用 ak.invite.cancel 的 invite_cancel_payload:invite_id、与持久化目标完整相等的 invitee_account_id、target_state 及该 schema 允许的诊断字段。invitee 本人拒绝写入 rejected;inviter 或获授权管理 actor 撤销写入 revoked。reducer MUST 从 Invite 前态确认 exact invitee,第三方/token placeholder 或缺少 invitee 的前态 MUST 以 failed_precondition / invite_kind_requires_revoke 原子拒绝;不得信任请求补出的身份。该 Event 只推进 Invite lifecycle,不写 member.state。
  • ak.invite.revoke 使用独立 invite_revoke_payload,只推进 Invite lifecycle;指向尚未绑定账号的 3PID placeholder 时 MUST NOT 携带 invitee_account_id。取消或撤销不会合成 membership leave 写入。完整终态规则见 governance-objects.md §5.3。

7. 私有 Invite Delivery

客户端在 ak.invite.create 被邀请方 Station 接受后,必须把 raw introduction_evidence 交给邀请方 Station 启动私有投递:

POST /_arkret/self/invites/dispatch
operation_id = ak.self.invites.command.dispatch.v1

请求 body 使用 ak.schema.invite_delivery_request.v1#/$defs/self_invite_dispatch_request_body,只携 schema=ak.schema.invite_delivery_request.v1、invite_event_id、invite_address、introduction_evidence 与 idempotency_key。服务端 MUST 以 invite_event_id 读取自己已接受并持久化的 canonical Event bytes;客户端不得回声、代签、重新 author 或重建该 Event。Event 未被本服务接受,或持久化 Event 的签署者不等于当前认证 session actor, MUST 以 active failed_precondition 拒绝;未激活的 invite_event_unaccepted 与 invite_event_actor_mismatch 不得发射。两类拒绝 MUST NOT 产生任何投递、outbox 入队或 holder-private 写入。由于 self wire 不再承载 Event bytes,不存在客户端 Event bytes 不一致的协议分支。

这个 self operation 只接受 raw evidence 并启动投递,不把它写入 Realm history。目标 invite_address.account_id.station_id 等于本机 service id 时,服务端 MUST 从下述接收验证的第 4 步开始执行同一套 验证、receive policy 与 holder-private projection;第 1–3 步是 service-to-service 专属绑定,本地分支 MUST 以”已认证 self session + 上述两条 accepted-event 前置”作为等价绑定,MUST NOT 合成 federation trust header、伪造 peer session 或自签 S2S 认证材料来走 peer 路径。本地已接受 Event 的 producer / admission 验证可以复用其准入结果;两条投递分支均不重放 Realm 授权闭包,也不依赖接收方持有 Realm 状态。目标为其它 Station 时,服务端 MUST 用持久化 Event 与请求中的寻址和 introduction evidence 构造 exact canonical request body, 交给下述 peer operation,并以 body 内 idempotency_key 绑定 durable retry / outbox。客户端在投递结果不确定时 MUST 用同一 body 与同一 idempotency_key 重试,不得替换 evidence 或 invite_event_id。

邀请方 Station 使用:

POST /_arkret/peer/invites
operation_id = ak.peer.invites.command.submit.v1

request body 为 ak.schema.invite_delivery_request.v1。接收方 Station MUST:

该 peer request 在 invite_commit 后携可选 producer_signer_fact,其出现当且仅当原 Commit 携 producer_signer_fact_digest;新普通 Human Invite 接纳必须已有该 digest。发送方 MUST 从 Event/Commit 同事务冻结的 admission archive 读取原 fact,禁止以 current key 或重新签名补材。接收方 MUST 独立核原治理签名/内容 ID、fact digest 与原 Event Ed 后再处理下列步骤;缺材以既有 Unavailable 拒绝且零 holder 写入。最小元数据只向既有 exact invitee AccountId 的 Station 定向披露,不扩大成员扫描或 PCR history 权限,也不写入 holder delivery typed current value。self dispatch 请求不变,本地已接受分支复用原事实,不能合成 peer 身份。

  1. 验证 service-to-service authentication,绑定 Source/Destination service did_core_id、trust domain、Content-Digest 与 idempotency key;接收方从已验证的 exact canonical body bytes 内部计算 request digest。
  2. 验证 Destination-Service-ID == invite_address.account_id.station_id。
  3. 验证 invite_address.service_resolution,要求完整证据的 service_id 等于 invite_address.account_id.station_id、adapter 投影 project(did) 等于该 did_core_id,并校验 freshness、service kind 与实际 target URL;carrier 不能单独授权投递。
  4. 验证 invite_event.kind 为 ak.invite.create、内容绑定的 Event / Invite ID 与 Realm ID,并按 federation.md §3 的“非治理接收方以治理签名为准”验证:producer proof 自身一致(event_digest、verification_method 投影与 human 设备 fragment);invite_commit 的治理签名有效,且其签发方在已验证的 authority chain 中是当时的 current governance Station;invite_commit.event_ref 与 invite Event 逐字对应。接收方不为此查询外站 current human 设备 key 或 PCR;新普通 Human Invite MUST 用随附原 fact 核 digest 与 Event Ed,inviter 恰为本站托管账号也使用原不可变事实,不能以 current key 改写历史来源。不得相信发送者自报公钥、仅使用 Source-Service-ID,或要求账号原站在线。保留目标、有效期及重放约束。 投递仅证明已认证发送者发出邀请,不验证或宣称其 Realm 管理权限、成员资格或邀请 durable acceptance。接收方 MUST NOT 为投递求值成员级 Realm 授权闭包、要求本地 accepted RealmCommit 或获取 Realm peer dependencies。请求不承载邀请专用 authority-commit bundles;普通 authority-commit、RealmCommit 签名和 signer authority 准入规则保持不变。正常加入 / 同步负责 Realm 授权及 durable acceptance,投递不得物化 Realm、membership、accepted RealmCommit、projection 或 checkpoint。 本步在 holder 查询、policy、consent、quota 与任何写入之前执行。结构错误返回 schema_violation,无效签名或 proof 绑定返回已注册的 signature_invalid;请求体仍受现有 8 MiB 上限约束。未能验证的 authority ref 不得作为任何可信状态或授权依据。
  5. 验证 invite_event.payload.invitee_account_id == invite_address.account_id,必须比较完整 AccountId。
  6. 验证 durable invite Event 未携带独立 route material;可选 route_assistance 只存在于 delivery transport,MUST NOT 要求它写入或匹配 durable Event,也 MUST NOT 把它当作授权证据。
  7. 验证 introduction_evidence,并核对 introduction_evidence_digest。consent_grant 必须是 exact invitee AccountId 给 inviter 的 active invite / any grant dot;handle_claim 必须逐字绑定 invite_address.account_id、issuer / Directory trust、domain allowlist、expiry 与 audience。分类顺序固定为:有效高信任 evidence,其次有效 handle_claim,其次接收端派生 same_station,最后 explicit_address。派生 same_station 只比较已验 invite Event account Actor 的 account_id.station_id 与 invite_address.account_id.station_id,不得使用 Source-Service-ID 或实际 ingress service。证据无效且不满足同 Station 时降级为低信任 explicit_address,不得直接通知或物化 membership。
  8. 计算 effective receive policy:先取 subject 私有 invite_receive_policy,再与 §5.2 receive_policy_constraints 及适用组织 / Realm 约束求交集。随后查 denied_actor_ids(与已验 invite Event 的完整 inviter ActorId 精确匹配;命中即 drop 且强制 opaque)与 denied_source_ids;再按 effective holder_allowed_introduction_kinds、handle_claim_behavior、explicit_address_behavior、unknown_invites 决定 drop / quarantine / notify。若 subject invite_receive_policy.consent_profile = require_explicit_consent 且第 7 步分类结果不是已验证 consent_grant,MUST 在此静默 drop 并强制 opaque——不 quarantine、不执行 quota 判定、不写任何 holder-private typed current result(../identity/consent-model.md §6.1 step 2)。判定为 quarantine 时,MUST 在写 quarantine typed current result 之前于 admission chokepoint 执行 ../identity/consent-model.md §6.1.1 的 per-holder 新来源 quota 判定(effective 值来自本节 subject new_source_quota 与 §5.2 部署 new_source_quota 的交集);超限即静默丢弃,不写 ledger、不写 typed current result,且与本节其它不可区分情形返回同一 opaque outcome。
  9. 返回 receive outcome:按 §5.1 分级披露。发现信任档、低信任档或 denied_actor_ids 命中时默认返回 generic status(opaque),MUST NOT 通过响应泄露 subject 是否存在或策略如何处理;高信任档且 disclosure.high_trust=outcome 时 MAY 在 disclosed_outcome 回送真实结果(delivered | blocked 两值)。仅当 subject 与部署约束都允许 disclosure.discovery_trust=outcome 时,handle_claim MAY 回送真实结果。invite 进入 holder quarantine inbox 时,无论信任档与 disclosure 取值,一律返回 status="deferred" 且 MUST NOT 携带 disclosed_outcome,并与“限速静默丢弃 / 超时丢弃 / holder 不存在 / holder policy deny”落在同一响应与 timing 等价类(../identity/consent-model.md §6.1.1);require_explicit_consent profile 下的静默 drop 是该等价类的「holder policy deny」成员,同样只返回 status="deferred" 且无 disclosed_outcome。

notify 分支的 holder-private 投递承载是 account-data 私有 typed current result,key 为 ak.account.invite_delivery(登记于 account-data-key-registry.json)。typed current result value 是明文 JSON,MUST 符合 ak.schema.invite_delivery.v1(invite-delivery.schema.json),不是 ak.schema.account_data_encrypted_value.v1 envelope:该 typed current result 由接收方 Station 在投递路径写入,服务端无法产出 holder 客户端加密的 envelope;entry 不携带任何 bearer 凭据,明文存储不改变信任边界。value 外层为 schema / updated_at / delivery_entries[],每个 entry 携带 invite_id / realm_id / inviter_account_id / authority_locator_hints / received_at / expires_at;inviter_account_id 必须逐字复制已接受 Invite Event 的完整账号,不能只存 principal 后猜 Station。authority_locator_hints 直接引用 ak.schema.realm_join_candidate.v1,必须有 1..8 项、按 service_id UTF-8 bytes 严格升序且以该 id 语义唯一;locator 不复制 realm_id 或时间,scope 与有效期只取 enclosing delivery entry。invite 未过期或 endpoint 可达都不能替代 nonce-bound current assertion。

写入语义是封闭的:

  • 该 typed current result 是 ../models/account-data.md §5 的 server-versioned CAS whole-value register:每次写入携带 expected_server_revision,冲突时写入方 MUST 重读当前值、按本节规则重新合并后重试,重试 MUST 有界(至多 3 次);重试耗尽 MUST 放弃本次投递写入并以内部冲突失败,MUST NOT 以 stale revision 强行覆盖。
  • 每次写入 MUST 先清除 expires_at <= now 的过期 entry,再按 invite_id 去重(同一 invite_id 的重复投递替换旧 entry,不重复占位),随后 append 新 entry;结果超过 200 条上限时 MUST 从 received_at 最旧的 entry 开始逐出,直至不超过 200 条。
  • entry 的 expires_at MUST 取自 invite Event payload 的 expires_at;payload 未携带时服务端 MUST 以该 Event 的 created_at 加 7 天兜底。expires_at <= now 的 entry 是 stale 的:客户端 MUST NOT 用它执行 accept,并 MUST 在读取时按 expires_at 过滤。
  • 写入被 CAS 接受时,服务端 MUST 在同一事务推进 account subscribe 的 Station-CAS 投影位置,使 holder 的全部 active devices 可通过顶层 account_data.station_cas 的 cursor-covered upsert 取得 accepted revision/value;删除使用显式 remove。服务端 MAY 另以 ak.account_data.update actor-private device update 做低延迟唤醒,该 envelope 使用 DeviceMessageSender::Service { sender_id } 分支:recipient_account_id == holder,sender_id 等于 recipient_account_id.station_id 与当前接收 Station 的 service identity;该分支不携 sender_account_id,不伪造 origin device,不走 holder device revocation gate,也不排除任一 active holder device。to-device 不是权威投影;离线或错过它的设备从 account subscribe baseline/catch-up 恢复,list/get 只作诊断与定点恢复。CAS 冲突或其它未接受写入不得推进投影或 fanout。
  • private delivery material MUST NOT 物化到 Invite 对象或任何 Realm state(../models/governance-objects.md §5.3);该 typed current result 是定向邀请送达被邀请方设备的唯一规范私有承载。定向邀请没有 bearer token:预览与接受的授权只来自治理 Station 已接受 Invite 对 exact invitee AccountId 的绑定——预览由被邀请方 Station 的服务间认证绑定该账号,接受由被邀请账号 producer 签名的 join Event 证明。

7.1 加入前预览(normative)

加入前预览与加入准备使用同一个权威来源:Realm 的 current governance Station。邀请方 Station、被邀请者自己的 Station、其它成员 Station、独立 Directory 与实际投递使用的 Source-Service-ID 只能提供 authority_locator_hints, MUST NOT 成为预览来源;locator 与定位规则见 ../governance/join-policy.md §6 与 authority-commit-log.md §7。

调用方式固定为自己的 Station 代理并验证:

  • 客户端只调用 ak.self.realm_join.read.preview.v1(POST /_arkret/self/realm-joins/preview)。请求体是 closed realm-join-intake.schema.json#/$defs/self_preview_request_body:request_id 与 target。邀请目标的 target.realm_id、target.invite_id 与 target.authority_locator_hints 逐字取自 §7 的 ak.account.invite_delivery entry;非邀请目标只携 realm_id 与取自 Directory discovery projection 的 hints。
  • 自己的 Station 以 target.realm_id 为唯一 Realm scope,用自己生成的 nonce 经 ak.open.realm_authority.read.bundle.v1 取得 RealmAuthorityBundle,验证 genesis、连续 handoff chain 与未过期的 nonce-bound current_assertion,由此确定 current governance Station。该 Station 就是本服务时 MUST 走等价的本地 路径,MUST NOT 合成 federation trust header 或自签 S2S 材料;否则调用 ak.peer.realm_join.read.preview.v1(POST /_arkret/peer/realm-joins/preview),请求体只携 request_id、 realm_id、requester_account_id 与可选 invite_id。调用方 MUST 使用 §3.2 的服务间认证并绑定 Source/Destination service DID、trust domain 与 Content-Digest,Destination-Service-ID 等于已验证的 current governance Station,requester_account_id 是已认证会话的完整账号且其 station_id 等于 Source-Service-ID。 MUST NOT 透传被请求者的本地 bearer / session 凭据。

current governance Station MUST:

  1. 确认自己是 realm_id 的 current governance Station;携 invite_id 时在自身已接受状态中定位该 invite, 要求其处于 live 状态并逐字绑定 requester_account_id 的完整 AccountId;
  2. 求值 effective ak.realm.preview_policy 对该 audience(受邀者或非受邀者)的披露范围,并只按其 fields 披露;
  3. 返回 closed peer_preview_outcome(request_id、preview)。响应 MUST NOT 携带 authority bundle、 join_candidates、source_refs、stale 或 divergent,也 MUST NOT 携带正文历史、成员列表、policy 原文、 隐藏 edge 或 E2EE 明文。

策略允许但没有可披露内容时,MUST 返回只含必填成员的最小 preview,MUST NOT 因此扩张读取。未知 Realm、 非 current governance Station、未知/不匹配/已终态的邀请、错误来源以及 Realm 未声明有效 preview policy,MUST 共用一个与不存在不可区分的失败,并按 §3.2 同口径固定 timing bucket。current governance Station 不可达是可重试的 上游失败,MUST NOT 换源。

自己的 Station 返回 closed self_preview_outcome:逐字回显 request_id,给出本 Station 以自己 nonce 验证过的 authority_bundle 与 governance Station 披露的 preview。客户端 MUST 核对 authority_bundle.realm_id 与 preview.realm_id 都等于请求的 target.realm_id 后才展示;MUST NOT 直连来源 Station、MUST NOT 选择转发候选, 也 MUST NOT 把预览当作 membership 或加入承诺。预览成功不产生任何加入副作用;正式加入走 ak.self.realm_join.command.prepare.v1 与 federation.md §5.3,加入 Event 经 self Event submit 按 authority-commit-log.md §4 同步取得治理结果;结果不确定时以相同 Event bytes 精确重试, 协议不另设加入申请状态读取面。 邀请本身不保证存在名称或头像;display_name 等只是策略许可后的展示信息。

8. Describe Capabilities

支持 invite addressing 的 Station MUST 广告能展开出下列精确 http_json pair 的 registered operation bundle:

  • ak.self.invite_locator.command.issue.v1
  • ak.self.invite_locator.command.rotate.v1
  • ak.self.invite_locator.command.revoke.v1
  • ak.self.invites.command.dispatch.v1
  • ak.open.invite_locator.read.resolve.v1
  • ak.peer.invites.command.submit.v1

它同时 MUST 在 supported_features[] 声明 ak.feature.invite_addressing.v1,并在 registered invite_addressing 字段给出可协商能力:

{
"invite_addressing": {
"supported_introduction_kinds": [
"locator_ref",
"consent_grant",
"shared_realm",
"handle_claim",
"same_station",
"explicit_address"
],
"handle_claim_max_behavior": "quarantine",
"explicit_address_max_behavior": "drop"
},
"receive_policy_constraints": {
"policy_version": "2026-06-21",
"applies_to": ["invite_delivery", "contact_request"],
"deployment_allowed_introduction_kinds": [
"locator_ref",
"consent_grant",
"shared_realm",
"handle_claim"
],
"handle_claim_max_behavior": "quarantine",
"explicit_address_max_behavior": "drop",
"allowed_handle_domains": ["acme.example"]
}
}

Directory 服务若支持 handle lookup,也 MAY 在 ServiceDescribe 或 ak.find.directory.read.describe.v1 的扩展字段中声明:

{
"x_handle_resolution": {
"invite_enabled": false,
"member_add_enabled": false
}
}

base clients MUST NOT require resolve_handle(intent="invite" | "member_add") to create or deliver an invite.

9. Handle 与 Mention 边界

Realm 内 mention 不依赖公网 handle resolve。客户端在用户输入 @alice:acme.example 时 MUST 先从当前 Realm roster、MemberIdentity subject disclosure、内联 signed handle_claims[] 或本地已授权 claim cache 中解析到 subject_account_id。发送 Message 前必须持久化 DID-committed mention reference;handle 字符串只能作为 audit / search metadata。