Binary DB v0:服务端与策略记录
展开 Patchset、attestation、Actor、评审、策略、豁免、Plan、Repository 注册表与 Worker Job 权威。
适用人群: 服务端、runner、策略与恢复实现者
本章讲的是以仓库为作用域的远端工作流权威,以及独立的、以安装为作用域的 Repository 注册表。数值索引只在各自声明的根里才有意义;Worker Job 的身份和引用都留在路由到的那个 Repository 权威内部。
服务端 Patchset 记录#
TASK_PATCHSET_INDEX_RECORD_SIZE = 8
TaskPatchsetIndexRecord — task_patchset_index.bin:
u32 latest_patchset_index_plus1
u16 patchset_count
u16 reserved0
CHANGE_PATCHSET_INDEX_RECORD_SIZE = 8
ChangePatchsetIndexRecord — change_patchset_index.bin:
u32 latest_patchset_index_plus1
u16 patchset_count
u8 next_patch_ordinal
u8 reserved0SERVER_PATCHSET_RECORD_SIZE = 65
ServerPatchsetRecord — patchset.bin:
u8 patchset_meta
u8 patch_ordinal
u8 change_ordinal
u8 reserved0
u32 change_index
u32 previous_task_patchset_index_plus1
u32 previous_change_patchset_index_plus1
u32 base_snapshot_index
u32 revision_snapshot_index
u64 created_at_s
u64 ci_completed_at_s
u32 ci_run_seq
u16 ci_selected_suite_count
u16 ci_suite_result_count
u16 ci_blocking_failure_count
u8 ci_status_bits
u64 summary_offset
u16 summary_len
u32 ci_worker_job_index_plus1前 51 字节是完整的定长 Patchset/紧凑 CI 区域:其中前 32 字节是加宽后的 Patchset 身份前缀,修正后的紧凑 CI 尾部恰好是 19 字节。这两个区域分别是 u32-time-v0 前身那 28 字节身份前缀和 15 字节紧凑 CI 尾部的零扩展形式。追加的 summary 定位符恰好是 10 字节。最后的 Worker Job 定位符恰好是 4 字节,合起来构成完整的 65 字节记录。这些区域 都没有隐式填充。summary_len 的取值范围是 1..65535, summary_offset 指向 patchset_summary_payload.bin 中恰好这么多个 非空 UTF-8 字节。Patchset summary 是这个可评审 Patchset 由人撰写的描述;它不是 Task intent、不是 Snapshot 消息、不是 Tree 差异统计、不是 Review 文本,也不是 Policy 输出。
patch_ordinal 在 (change_index, patch_ordinal) 上唯一。取值 0..63 在完整的所属 Change 身份之下渲染为 P-01..P-64。 ChangePatchsetIndexRecord.next_patch_ordinal 是已分配序号的最大值加一, 超过 64 之后就拒绝分配;删除或省略绝不会复用一个序号。 ChangePatchsetIndexRecord.latest_patchset_index_plus1 和 previous_change_patchset_index_plus1 遵循以 Change 为作用域的 Patchset 序号。 TaskPatchsetIndexRecord 只表示物理清单:它的 latest 指针和 每一个 previous_task_patchset_index_plus1 都遵循已提交的物理 Patchset 记录顺序,既不分配、也不暗示以 Task 为作用域的 Patchset 序号。 恢复过程从已提交的记录顺序重建 Task 清单,并从以 Change 为作用域的身份 重建每条 Change 链、计数和 next 序号。
Patchset 创建时先追加 summary 字节并 fsync,之后才追加 定长的 Patchset 提交记录。Patchset 的身份、属主、序号、Snapshot 引用、创建时间以及 summary 定位符/字节都是不可变的。紧凑 CI 字段和 ci_worker_job_index_plus1 只能在声明的 Worker Job 变更边界之下, 通过整条记录替换来改变。被打断的追加可能只留下未被引用的尾部 summary 字节,恢复时会忽略它们。已提交但缺失、为空、重叠或包含非法 UTF-8 的 summary 区间属于损坏。
ci_completed_at_s 是非负的 u64 Unix 秒时间戳。 ci_run_seq = 0 表示还没有 CI 运行启动过。ci_run_seq 非零而 ci_completed_at_s = 0 表示一次进行中的运行,此时要求所有状态 和计数字段都为零。完整的证据要求完成时间非零、 运行序列非零,且整体状态不是 none。
ci_worker_job_index_plus1 = 0 表示该 Patchset 没有选中任何由 Worker Job 支撑的 CI 运行;这包括遗留的紧凑 CI 证据和 内联 CI 运行。非零值在与该 Patchset 同属一个服务端 Repository 权威的 worker_job.bin 中解析为 index + 1。它必须指向一个 未被墓碑标记、类型恰好是 patchset.ci 的 Job,且该 Job 定长的 patchset_index_plus1 要能解析回这个确切的 Patchset。这条同根关系里不涉及任何 Repository ID 或 Repository 索引。
该定位符选中的是这个 Patchset 已提交的最大 worker_job_index。之后 重跑会追加一个新 Job,并用那个新的本地索引替换整条 Patchset 记录;更早的 Job 仍然是 Job 历史,但在被取代之后就不能再覆写紧凑 CI 证据。 在终态完成时,只有在重新确认那个完成的 Job 仍是定位符选中的目标之后, 才可以用 Job 结果替换紧凑 CI 字段。
ServerPatchsetRecord.ci_status_bits:
bits 0..1 overall_status
bits 2..3 tests_status
bits 4..5 lint_status
bits 6..7 reserved = 0
Each two-bit status:
00 none
01 pass
10 fail
11 errorci_suite_result_count 不得超过 ci_selected_suite_count, ci_blocking_failure_count 也不得超过 ci_suite_result_count。各分项 状态要求整体证据已完成。这些字段只存放紧凑的 Patchset CI 证据。队列状态、尝试次数、固定的结果和重试错误 类别都留在同一个 Repository 的定长 Worker Job 家族里。lease 凭据、请求物化、详细结果、日志、制品和调度器配置都在 Binary DB v0 之外。
源端的 author_mode、publish_state 中 published/superseded 的那部分,以及 pending/非 pending 的 Policy 缓存状态,都按下文定义编码进 patchset_meta。 源端的 selected-for-landing 投影会按 Remote Change 指针规则归一化, 它不是 Patchset 状态。源端的 diff_stats 不做存储:它是通过精确比较 base 和 revision Snapshot 的 Tree 重新算出来的。除了"bin 到 bin 的转换接纳条件"一节里那个 确切具名的遗留全零闸门之外,转换器必须拒绝与该比较结果不一致的 给定值。非 pending 的 evaluation_state 就是最新存活的 Policy Decision 类别;Patchset 上的 pending 位用来区分新建或已失效的缓存 和一个更早的非 pending 决定。任何 Patchset JSON payload 都不具备权威性。
Patchset 存储由服务端权威负责。本地工作流权威不定义任何 Patchset 文件。
服务端 Attestation 记录#
TASK_ATTEST_INDEX_RECORD_SIZE = 8
TaskAttestIndexRecord — task_attest_index.bin:
u32 latest_attest_index_plus1
u16 attest_count
u8 next_attest_ordinal
u8 reserved0
PATCHSET_ATTEST_INDEX_RECORD_SIZE = 8
PatchsetAttestIndexRecord — patchset_attest_index.bin:
u32 latest_attest_index_plus1
u16 attest_count
u16 reserved0SERVER_ATTEST_RECORD_SIZE = 24
ServerAttestationRecord — attest.bin:
u8 attest_meta
u8 attest_ordinal
u8 patch_ordinal
u8 change_ordinal
u32 patchset_index
u32 previous_task_attest_index_plus1
u32 previous_patchset_attest_index_plus1
u64 created_at_sAttestation 那四个有限的要求值存放在 attest_meta 的第 3 到第 6 位,绝不放进 payload。如果源端某条紧凑 Attestation 的时间和要求位全为零,就视为不存在,不写出 v0 记录。 存在的源端紧凑 Attestation 是以 Change 为作用域的可变源状态; 遗留格式并不会把选中 Patchset 的历史持久化到 attested_at_s。在持有一份一致的锁定源快照期间,转换 会通过所属 Change 权威的当前 selected_patchset_number 解析该行, 并写出一条 v0 Attestation,其 patchset_index 指向那个确切的 Patchset。序号到目标索引的查找属于 转换器本地状态,不会产生任何历史选择字段、记录、bin 或 payload。这个查找不会让当前 Change 的选择变成临时的:同一个源指针 会独立地填充上文定义的既有 RemoteChangeRecord.selected_patchset_index_plus1 权威。当前 selected 指针缺失、无效、不属于该属主或不唯一,都会 fail closed。源端 Attestation 的任何 author mode 都必须等于所绑定 Patchset 的 author mode,因为 v0 不会把它复制一份。
Actor 与 Review 记录#
ACTOR_RECORD_SIZE = 36
ActorRecord — actor.bin:
u8 actor_meta
u8 reserved0
u16 payload_len
u64 payload_offset
u64 actor_key_hash
u64 created_at_s
u64 last_seen_at_s对 bin 到 bin 的转换而言,这条既有的定长记录加上既有的 ActorPayload 就是完整的 Actor schema;不存在仅用于转换的 Actor bin 或 通用 payload。源端每一个互不相同的非空评审者身份,都会逐字节 存为 ActorPayload.user_name_bytes,同时 user_id_len = 0、 email_len = 0、memo 为空。转换器不会去裁剪、折叠大小写,也不会把 Name <email> 这类展示字符串解析成臆造的组成部分。除非有结构化的源字段能 证明是别的类别,否则 Actor 类别就是 unknown。 通过 requested_groups 提供的非空身份属于结构化的团队 证据,使用 team 类别。类别相同且身份字节相同的会复用同一个 Actor;字节不同则仍是不同的 Actor。
这份必填 payload 是身份权威和冲突证据,不是 可选的 Review 注解。它的长度必须放得进既有的 u8 user_name_len 和 u16 payload_len;溢出或非法 UTF-8 会 fail closed。 actor_key_hash 是对确切的 user_name_bytes 计算的 FNV-1a-64:
actor_key_hash = fnv1a64(user_name_bytes)
offset_basis = 0xcbf29ce484222325
prime = 0x00000100000001b3ActorLookupIndexRecord 可能返回哈希冲突的候选。查找 只有在 actor_kind 和 ActorPayload 字节都精确比对通过之后才接受某个候选; 仅凭哈希绝不会合并身份。created_at_s 是引用该 Actor 的源端 ReviewRecord.created_at_s 的最小值, last_seen_at_s 是最大值。当被接纳的遗留源格式没有 Review 时间时,这两个字段都为零,表示历史时间未知。 提供的有效时间会在推导中保留,提供的无效时间会 fail closed;绝不会拿转换时间来顶替。
TASK_REVIEW_INDEX_RECORD_SIZE = 8
TaskReviewIndexRecord — task_review_index.bin:
u32 latest_review_index_plus1
u16 review_count
u16 reserved0
PATCHSET_REVIEW_INDEX_RECORD_SIZE = 8
PatchsetReviewIndexRecord — patchset_review_index.bin:
u32 latest_review_index_plus1
u16 review_count
u8 next_review_ordinal
u8 reserved0SERVER_REVIEW_RECORD_SIZE = 40
ServerReviewRecord — review.bin:
u8 review_meta
u8 review_ordinal
u8 patch_ordinal
u8 change_ordinal
u32 actor_index_plus1
u32 patchset_index
u32 previous_task_review_index_plus1
u32 previous_patchset_review_index_plus1
u64 payload_offset
u16 payload_len
u16 reserved0
u64 created_at_sReview 的各种动作变体,都由一个基础动作加上 review_meta 里的固定修饰位 组成。确切的源端映射如下:
request -> request
comment -> comment
task_comment -> comment + task_lane
code_review_summary -> comment + code_review_summary
approve -> approve
task_approve -> approve + task_lane
request_changes -> request_changes
task_request_changes -> request_changes + task_lane
defer -> comment + defer
task_defer -> comment + task_lane + defer
dismiss -> dismiss其他任何写法,或者非法的基础动作/修饰位组合,都会 fail closed。 独立的 blocking 位在每一种映射中都会被保留。普通 Review 的 actor_index_plus1 指向它的评审者 Actor。恰好带一个 requested_groups 值的评审请求使用 request 动作,并指向对应的团队 Actor; 活跃的 v0 schema 无法在一条 Review 里编码多个组。它源端的 reviewer 可以缺失,也可以包含与那唯一一个非空组完全相同的 UTF-8 字节。相同的情况下它只是一份冗余的遗留投影:转换会创建或复用那一个团队 Actor 并存下一个 actor_index_plus1;它不会创建第二个 Actor 或 Review。如果转换器遇到不同的评审者、空身份、 零个组,或一个请求里超过一个 requested group,它会 fail closed, 而不是凭空造出 Review 行或 payload。Review 的评论或请求备注 文本仍留在 ReviewPayload 里。
review_ordinal 在 (patchset_index, review_ordinal) 上唯一。取值 0..63 在完整的所属 Patchset 身份之下渲染为 R-01..R-64。被 接纳的、以 Change 为作用域的遗留 Review ID,在挪到该 Review 确切的 Patchset 之下时会保留其确切的数字后缀;同一个 Patchset 内允许存在空档, 但不允许序号重复。PatchsetReviewIndexRecord 负责 next 序号分配,其 latest/previous 链接遵循以 Patchset 为作用域的 Review 序号。TaskReviewIndexRecord 和 previous_task_review_index_plus1 只保留 Task 的物理清单。 恢复过程重建每条 Patchset 链、计数和 next 序号,同时不会 给源端的 Review 身份重新编号。
Policy 与 Waiver 记录#
TASK_POLICY_INDEX_RECORD_SIZE = 8
TaskPolicyIndexRecord — task_policy_index.bin:
u32 latest_policy_index_plus1
u16 policy_count
u16 reserved0
PATCHSET_POLICY_INDEX_RECORD_SIZE = 8
PatchsetPolicyIndexRecord — patchset_policy_index.bin:
u32 latest_policy_index_plus1
u16 policy_count
u8 next_policy_ordinal
u8 reserved0POLICY_DECISION_RECORD_SIZE = 32
PolicyDecisionRecord — policy.bin:
u8 policy_meta
u8 policy_ordinal
u8 patch_ordinal
u8 change_ordinal
u32 patchset_index
u32 previous_task_policy_index_plus1
u32 previous_patchset_policy_index_plus1
u32 first_check_index_plus1
u16 check_count
u16 reserved0
u64 created_at_spolicy_ordinal 在 (patchset_index, policy_ordinal) 上唯一,而不是在 所属 Task 内唯一。取值 0..63 在完整的所属 Patchset 身份之下渲染为 K-01..K-64。PatchsetPolicyIndexRecord.next_policy_ordinal 是 已分配序号的最大值加一,超过 64 之后就拒绝分配;删除 或墓碑标记绝不会复用一个序号。因此一个 Task 跨多个 Patchset 可以拥有超过 64 条 Policy Decision,只要不超过它 u16 policy_count 的 容量即可,而每个 Patchset 仍然以 64 为上限。
PatchsetPolicyIndexRecord.latest_policy_index_plus1 和 previous_patchset_policy_index_plus1 遵循以 Patchset 为作用域的 Policy 序号。 TaskPolicyIndexRecord 只保留完整的 Task 清单:它的 latest 指针和每一个 previous_task_policy_index_plus1 都遵循已提交的物理 Policy 记录顺序,既不分配、也不暗示以 Task 为作用域的 Policy 序号。 恢复过程从已提交的记录顺序重建 Task 清单,并从以 Patchset 为作用域的身份 重建每条 Patchset 链、计数和 next 序号。
POLICY_CHECK_RECORD_SIZE = 8
PolicyCheckRecord — policy_check.bin:
u8 check_kind
u8 check_status
u16 subject_ordinal
u32 detail_flags从 first_check_index_plus1 开始的那些行保留源端的检查顺序, 并且是完整的定长 Policy 检查权威。subject_ordinal 和 detail_flags 的确切含义如下:
check_kind 0..7:
subject_ordinal = 0
detail_flags = 0
check_kind 8 ci_rollout_phase:
subject_ordinal = exact rollout phase in 0..65535
detail_flags = 0
check_kind 9 ci_patchset_suite:
subject_ordinal = one-based position of the exact suite ID in the
normalized UTF-8-byte-sorted exact Policy tuple catalog
detail_flags bit 0 = blocking suite
detail_flags bit 1 = informational suite
detail_flags bits 2..31 = 0ci_rollout_phase 是 CI 强制策略的数值 rollout 阶段。它 不是 Task、Change、Patchset 或 Land 的生命周期状态,它本身也不 指代某个 CI 套件。对于每一个被接纳的遗留 phase-0 投影,源端的检查 名称恰好是 ci_rollout_phase;转换会写入 check_kind = 8, 保留源端的检查状态,写入 subject_ordinal = 0,并写入 detail_flags = 0。
遗留转换只接受下列确切的 (catalog, blocking set, informational set, phase message) 元组。 目录比较使用归一化后的 UTF-8 字节序。位于 blocking 集合中的套件获得 detail 位 0;位于 informational 集合中的套件获得 detail 位 1。
catalog = [rust_core]
blocking = [rust_core]
informational = []
message = CI rollout phase 0 blocks `rust_core` and keeps none visible as non-blocking surfaces.
catalog = [full_repo_contract]
blocking = [full_repo_contract]
informational = []
message = CI rollout phase 0 blocks `full_repo_contract` and keeps none visible as non-blocking surfaces.
catalog = [full_repo_contract]
blocking = [full_repo_contract]
informational = []
message = CI rollout phase 0 blocks `full_repo_contract` and keeps none visible as non-blocking surfaces. Future promotions are modeled as phase1: `full_repo`.
catalog = [package_smoke, preflight, recent_regression, stable_smoke]
blocking = [package_smoke, preflight, stable_smoke]
informational = [recent_regression]
message = CI rollout phase 0 blocks `package_smoke`, `preflight`, `stable_smoke` and keeps `recent_regression` visible as non-blocking surfaces.
catalog = [package_smoke, preflight, recent_regression, stable_smoke, task_batch]
blocking = [package_smoke, preflight, stable_smoke]
informational = [recent_regression, task_batch]
message = CI rollout phase 0 blocks `preflight`, `stable_smoke`, `package_smoke` and keeps `recent_regression`, `task_batch` visible as non-blocking surfaces. Future promotions are modeled as phase1: `recent_regression`, `task_batch`, phase2: `full_repo`.
catalog = [package_smoke, preflight, recent_regression, stable_smoke, task_batch, tg1_required]
blocking = [package_smoke, preflight, stable_smoke, tg1_required]
informational = [recent_regression, task_batch]
message = CI rollout phase 0 blocks `preflight`, `stable_smoke`, `package_smoke`, `tg1_required` and keeps `recent_regression`, `task_batch` visible as non-blocking surfaces. Future promotions are modeled as phase1: `recent_regression`, `task_batch`, phase2: `full_repo`.
catalog = [package_smoke, preflight, recent_regression, stable_smoke, task_batch, tg1_required]
blocking = [package_smoke, preflight, stable_smoke]
informational = [recent_regression, task_batch, tg1_required]
message = CI rollout phase 0 blocks `preflight`, `stable_smoke`, `package_smoke` and keeps `recent_regression`, `task_batch`, `tg1_required` visible as non-blocking surfaces. Future promotions are modeled as phase1: `recent_regression`, `task_batch`, phase2: `full_repo`.
catalog = [package_smoke, preflight, recent_regression, stable_smoke, task_batch, tg1_required]
blocking = [package_smoke, preflight, stable_smoke]
informational = [recent_regression, task_batch, tg1_required]
message = CI rollout phase 0 blocks `preflight`, `stable_smoke`, `package_smoke` and keeps `tg1_required`, `recent_regression`, `task_batch` visible as non-blocking surfaces. Future promotions are modeled as phase1: `recent_regression`, `task_batch`, phase2: `full_repo`.
catalog = [package_smoke, preflight, recent_regression, stable_smoke, tg1_required]
blocking = [package_smoke, preflight, stable_smoke]
informational = [recent_regression, tg1_required]
message = CI rollout phase 0 blocks `package_smoke`, `preflight`, `stable_smoke` and keeps `recent_regression`, `tg1_required` visible as non-blocking surfaces. Future promotions are modeled as phase1: `recent_regression`, `task_batch`, phase2: `full_repo`.
catalog = [package_smoke, preflight, recent_regression, stable_smoke, tg1_required]
blocking = [package_smoke, preflight, stable_smoke, tg1_required]
informational = [recent_regression]
message = CI rollout phase 0 blocks `package_smoke`, `preflight`, `stable_smoke`, `tg1_required` and keeps `recent_regression` visible as non-blocking surfaces. Future promotions are modeled as phase1: `recent_regression`, `task_batch`, phase2: `full_repo`.阶段检查、确切的套件名检查、目录、blocking/informational 划分以及消息,必须与其中某一个元组一致。标签属于推导出来的 展示内容,不做持久化。不同的或有歧义的表示会 fail closed;转换器绝不会从自由文本里解析出阶段编号或套件分配。 原生 v0 写入方只有在显式的 Policy 配置直接提供了这些定长字段时,才可以使用 另一个数值阶段。
对 check_kind = 9,两个套件 detail 位中恰好置位一个。在 同一条 Policy 内,套件序号非零、唯一、完整,且不超过 该 Policy 确切元组目录的长度。它们并不受所属 Patchset 的 ci_selected_suite_count 约束:Patchset 那个字段是某一次 CI 运行的紧凑 证据,而不可变的 Policy 行保留的是历史评估结果,这些结果可能早于 那份证据,也可能使用了另一种被接纳的 blocking/informational 投影。转换会把源端的 ci_patchset_suite_<suite-id> 名称对照确切的 Policy 元组目录做解析; 套件身份缺失、重复、不完整或有歧义都会 fail closed。 Policy 检查的 label 和 message 是展示投影,不做 存储。未知的检查名称/状态会 fail closed,而不是变成文本 payload。源端的 input_fingerprint 是缓存元数据,不是 Policy 权威; v0 会从定长的 Patchset、Attestation、Review、Waiver、CI 和 Policy 输入重新计算它,并不持久化源端的缓存键。
TASK_WAIVER_INDEX_RECORD_SIZE = 8
TaskWaiverIndexRecord — task_waiver_index.bin:
u32 latest_waiver_index_plus1
u16 waiver_count
u8 next_waiver_ordinal
u8 reserved0
PATCHSET_WAIVER_INDEX_RECORD_SIZE = 8
PatchsetWaiverIndexRecord — patchset_waiver_index.bin:
u32 latest_waiver_index_plus1
u16 waiver_count
u16 reserved0WAIVER_RECORD_SIZE = 44
WaiverRecord — waiver.bin:
u8 waiver_meta
u8 waiver_ordinal
u8 patch_ordinal
u8 change_ordinal
u32 patchset_index
u32 previous_task_waiver_index_plus1
u32 previous_patchset_waiver_index_plus1
u64 payload_offset
u16 payload_len
u16 rule_code
u64 created_at_s
u64 expires_at_sPlan 记录#
PLAN_RECORD_SIZE = 48
PlanRecord — plan.bin:
u8 plan_meta
u8 reserved0
u16 payload_len
u64 payload_offset
u32 latest_revision_index_plus1
u32 published_plan_index_plus1
u32 published_latest_revision_index_plus1
u64 created_at_s
u64 updated_at_s
u64 published_at_sPLAN_REVISION_RECORD_SIZE = 56
PlanRevisionRecord — plan_revision.bin:
u8 revision_meta
u8 reserved0
u16 payload_len
u16 revision_number
u16 item_count
u64 payload_offset
u32 plan_index
u32 previous_revision_index_plus1
u32 item_start_index
u32 published_revision_index_plus1
u32 root_tree_pack_index_plus1
u32 root_entry_ordinal
u64 created_at_s
u64 published_at_sPLAN_ITEM_RECORD_SIZE = 16
PlanItemRecord — plan_item.bin:
u8 item_meta
u8 reserved0
u16 payload_len
u64 payload_offset
u32 line_numberPlan 的 revision 历史是一条线性链:
PlanRecord.latest_revision_index_plus1
-> PlanRevisionRecord.previous_revision_index_plus1
-> ...
-> 0Task 到 Plan 的绑定使用 Task 记录里数值形式的 revision 和 item 索引。 要确定是否真的可开成 Task,仍然需要与 PlanItemPayload.plan_item_ref_bytes 做比较。
服务端全局 Repository 注册表权威#
服务端全局的 Repository 注册表是一个以安装为作用域的 Binary DB 根。它绝不会嵌套在某个仓库权威内部,也绝不会被复制 进本地仓库。明确划归这个根的每一个文件,都使用 全局的四字节 layout_id = 1 头部。必需但为空的家族就是一个 只有头部的文件。这个根里的物理索引和另一个权威 根里数值相同的索引之间没有任何关系。
下文每一个 repository_index 都是直接指向这个根的 repository.bin 的稠密索引,也是该 Repository 唯一的主键。索引零是 合法的。Repository 记录只追加:已分配的索引绝不会被重新编号、移除、 复用或转移给另一个 Repository。*_index_plus1 使用全局的"缺省"编码。这个根绝不持久化任何指向仓库工作流、Worker Job 或共享内容 权威的数值索引。
前四个 Repository 索引是固定的:
0 ait-core
1 ait-server
2 ait-python
3 ait-node之后每一个 Repository 都从 4 开始,按下一个记录序号追加。 所有服务端路由、配置和运行期关系,都通过 repository_index 标识一个 Repository,绝不通过 Repository 名称。
活跃的服务端全局定长家族清单恰好只有 repository.bin。 它活跃的带类型 payload 清单恰好只有 repository_payload.bin, 它唯一活跃的索引是 repository_namespace.idx。 operational_public_sequence.bin、任何 Worker Job 定长文件,以及任何 Worker Job 索引,在这个全局根里都是禁止的。Worker Job 文件必须按下文声明的方式, 存在于每一个数值形式的服务端 Repository 权威中。任何 其他的运行期 .bin、payload 或 .idx 文件都会导致激活失败。
运行期时间戳是非负的 u64 Unix 秒。必填的 时间戳非零。可选的时间戳在缺省时为零,否则 非零。被接纳的 RFC 3339 时间戳会 归一化为 UTC;对非负值而言,小数秒会在整秒值做范围检查并 存储之前被有意丢弃。早于 Unix 纪元的值、大于 u64::MAX 的 整秒值,或无法解析的文本,都会 fail closed。Repository 和 Job 的 updated_at_s 不早于各自的 created_at_s。当某个 Job 被加锁时, created_at_s <= locked_at_s <= updated_at_s。
Repository 注册表记录#
注册表是以安装为作用域的权威,负责 Repository 身份和 路由元数据。它独立于每一个以仓库为作用域的工作流和 内容根。
OPERATIONAL_REPOSITORY_RECORD_SIZE = 33
OperationalRepositoryRecord — repository.bin:
u8 repository_meta
u8 lifecycle_kind
u8 namespace_ascii[2]
u8 policy_flags
u32 payload_len
u64 payload_offset
u64 created_at_s
u64 updated_at_sRepository 的主键是该记录的物理 repository_index;它 不会在记录或 payload 内部再复制一份。Repository 名称是非空、 不可变的 UTF-8 展示元数据,可以无限制地重复。名称绝不会被 当作身份或路由权威。非空的 namespace_ascii 值 在 active 和 retiring 记录之间唯一;为空是合法的,且没有 命名空间索引行。历史上的 purged 身份可能与后来某个存活的 Repository 共用一个命名空间,因此命名空间查找可能返回多个候选,必须 校验生命周期加上那确切的两个定长字节。repo_name 和 created_at_s 在创建之后不可变。策略、命名空间、生命周期 和 updated_at_s 只能在注册表锁之下通过整条记录替换来变更。
已激活的根至少有四条 Repository 记录。第零到第 三条记录的 payload 名称分别恰好是 ait-core、ait-server、ait-python 和 ait-node,且绝不会被墓碑标记;退役改用保留的 lifecycle_kind = purged 记录来表示。后来的记录可以重复其中任何一个 名称,但不会因此获得那条保留记录的身份。
逻辑上的 Repository 默认 Line 始终是确切的 UTF-8 main。它是 schema 常量,没有对应的定长或 payload 字段,会在暴露默认 Line 值的 API 边界上合成出来。原生创建流程无法选择 别的默认 Line。
Repository 权威目录路由#
配置好的服务端 Repository 权威父目录,用规范的无符号 十进制 repository_index 作为每个 Repository 的目录名:
<server-repository-authority-parent>/
0/ # ait-core
1/ # ait-server
2/ # ait-python
3/ # ait-node
4/
...零恰好写作 0;其他每一个目录名都不带前导零、 符号、空白、后缀、前缀或其他数字写法。Repository 名称绝不出现在权威目录名中。该目录是那个 Repository 的 工作流、内容、Plan 和 Worker Job 权威家族的路由容器。目录 本身不增加任何 .bin 记录;Worker Job 子布局在下文声明。
活跃的父目录里,为每一条未被墓碑标记的 Repository 记录恰好包含一个真实的、 非符号链接的目录,且没有任何文本别名目录。解析一个目录时,会把它的目录名 按 u32 解析,核实对应的 repository.bin 记录存在且未被墓碑标记,绝不按 Repository 名称扫描。压实过程保留每一个 Repository 目录名, 因为它保留了每一个 repository_index。
以 Repository 为作用域的 Worker Job 记录#
每一个规范的数值形式服务端 Repository 权威,都恰好包含一个 worker_job.bin 运行期队列权威。必需但为空的文件就是只有头部的文件。这个文件是那一个 Repository 的 Remote Binary 输入:它 不会被复制进本地 Repository,不是被排除在外的通用 job.bin,不是 Patchset 的紧凑 CI 证据,也不是队列投影。活跃的 v0 不定义任何 Worker Job payload 文件。
SERVER_WORKER_JOB_RECORD_SIZE = 52
ServerWorkerJobRecord — worker_job.bin:
u8 job_meta
u8 job_kind
u8 state_kind
u8 outcome_kind
u16 attempt_count
u16 max_attempts
u16 error_kind
u16 reserved0
u32 patchset_index_plus1
u32 snapshot_index_plus1
u64 available_at_s
u64 locked_at_s
u64 created_at_s
u64 updated_at_s所属的 Repository 隐含在规范的数值权威 目录里。Repository ID 和 repository_index 都不会在 定长记录里复制一份。物理上直接的 worker_job_index 记录序号,就是那个 Repository 内部 Worker Job 唯一的主键。记录只追加: 已分配的索引绝不会被重新编号、移除、复用或转移。 墓碑标记会保留它确切的槽位。
完整的安装级身份是 (repository_index, worker_job_index)。单独一个 worker_job_index 在它所路由到的 Repository 权威之外没有任何 含义,且 v0 不存储任何独立的公开或全局 Job ID。attempt_count <= max_attempts;两者都必须放得进 u16, 且 max_attempts 非零。job_kind 是定长权威;API 的 job_type 字符串是由它合成的,绝不会作为 Job 字节持久化。 available_at_s、created_at_s 和 updated_at_s 是必填的。只有 running 才有非零的 locked_at_s;其他每种状态都是 locked_at_s = 0。
那两个定长的领域引用字段有下面这套确切的、按 Job kind 区分的解释。 +1 保留零表示缺省。每一个非零引用都在 与该 Job 相同的数值 Repository 权威内部解析:
job_kind | API job_type | patchset_index_plus1 | snapshot_index_plus1 |
|---|---|---|---|
| 2 | content.gc | 零 | 零 |
| 3 | content.optimize | 零 | 零 |
| 4 | content.pack | 零 | 零 |
| 5 | land.process | 所属 Patchset | 零 |
| 6 | main-seed.refresh | 所属 Patchset | 先前 Snapshot 或零 |
| 7 | patchset.ci | 所属 Patchset | 零 |
| 8 | patchset.ci.aggregate | 所属 Patchset | 零 |
| 9 | policy.evaluate | 所属 Patchset | 零 |
| 10 | reconcile.repo | 零 | 零 |
| 11 | repo.ci | 零 | 选中的 Snapshot |
job_kind = 1 在活跃的 v0 中未分配。agent.turn.submit 需要一份 不透明的 turn 请求,它无法由既有的 Repository 领域权威表示, 因此在有带类型的 agent-turn schema 之前,它不是持久化的 Worker Job。其他任何 job_kind、本表未 分配却非零的字段、家族错误的目标、跨 Repository 的目标,或 被墓碑标记的目标,都会 fail closed。 land.process 从解析出的 Patchset 推导它的 Change 和 revision Snapshot。Job 在任何 Land 存在之前就已提交;执行时会针对逻辑上的 main、以固定的服务端默认 direct 模式创建 Land。 Patchset 相关的 Change 和 Snapshot 数据在其余情况下都从解析出的 Patchset 闭包推导。main-seed.refresh 使用逻辑上的 main,且只冻结 一个独立要求的先前 Snapshot。原生 repo.ci 会在提交 Job 之前冻结它选中的 Snapshot,同样在逻辑上的 main 上操作;这两种 kind 都不会持久化 Line 索引,也不会把 Snapshot 身份推迟给一个可变的 Repository head。
不存在变长的 Worker Job 请求或输入字节。job_kind 和那两个 定长引用就是完整的持久化执行选择器。只有当每一个影响执行的选择都已经提交到那些字段、或提交到被引用的既有领域权威之后, 写入方才可以提交一个 Job。触发 标签、传输标签、调度器资源键、优先级、重试延迟、 runner 上下文和物化出来的运行期 payload,都是重建出来的运行期 数据,不能改变那份持久化的选择。内容维护和 Repository 对账这两种 kind 会调用它们那一个由服务端拥有的 kind 级 操作,不带任何按 Job 的覆盖参数。Patchset CI 和聚合从 选中的 Patchset 及其经过校验的 Policy/CI 权威推导出套件 闭包。Repository CI 使用服务端拥有的 Repository CI 契约,在选中的 Snapshot 和逻辑上的 main 上操作。如果某个被请求的操作 无法在这些规则之下被精确重建,入队会在 分配 worker_job_index 之前失败。
运行期 Repository payload 文件#
服务端全局根和每一个服务端 Repository 权威都没有共享的 字符串池,也没有 operational_payload.bin。Repository 注册表的定位符 指向全局的 repository_payload.bin。Worker Job 没有 payload 定位符,也没有 payload 文件。
OperationalRepositoryPayload — repository_payload.bin:
u16 repo_name_len
u8 repo_name_bytes[repo_name_len]Repository 的 payload_len 非零,payload_offset 位于 BIN_HEADER_SIZE 或其之后;它恰好是 2 + repo_name_len,没有填充也没有 尾部字段。Repository 名称是精确合法的 UTF-8。完整的 Repository payload 最多 65,537 字节。
可重建的注册表与 Worker Job 索引#
本小节里的所有索引都是可选的可重建加速器。一个文件 使用的权威根和目标家族,恰好由它的文件名隐含决定。 repository_namespace.idx 属于服务端全局注册表。那两个 Worker Job 索引分别属于每一个数值形式的服务端 Repository 权威。持久化的行按声明顺序对所有字段排序。
OPERATIONAL_NAMESPACE_INDEX_RECORD_SIZE = 8
OperationalNamespaceIndexRecord — repository_namespace.idx:
u8 namespace_ascii[2]
u16 reserved0
u32 repository_index_plus1空命名空间字节 [0x00, 0x00] 没有索引行。非空且字节精确匹配的 命名空间候选,可能包含多个历史 purged 身份, 但最多只有一个 active 或 retiring 身份。
SERVER_WORKER_READY_INDEX_RECORD_SIZE = 12
ServerWorkerReadyIndexRecord — worker_ready.idx:
u64 available_at_s
u32 worker_job_index_plus1
SERVER_WORKER_STATE_INDEX_RECORD_SIZE = 8
ServerWorkerStateIndexRecord — worker_state.idx:
u8 state_kind
u8 reserved0
u16 reserved1
u32 worker_job_index_plus1worker_ready.idx 只包含存活的 queued 行。队列认领会在查找之后校验 本地 Job 索引、权威状态、确切的 available_at_s、尝试 预算,以及不存在存活的运行期 lease。worker_state.idx 包含每一个存活的 Job。在这两个文件里,worker_job_index_plus1 只在 持有该索引的同一个 Repository 权威内部解析。
不会为被墓碑标记的记录写 .idx 行。缺失、陈旧、重复 或损坏的索引会被丢弃,并从该权威的 worker_job.bin 重建。索引绝不会修复或覆盖定长记录。
安装范围的调度器会从注册表枚举 active 和 retiring 的 Repository 索引,从各个本地 worker_ready.idx 读取经过精确校验的候选,并在内存里按 (available_at_s, repository_index, worker_job_index) 归并它们。那次内存归并 是一次性的,绝不会作为全局 Job 索引、序列或 身份权威持久化。
运行期元数据与状态编码#
除非下文另有覆盖,可变的运行期 *_meta 字节使用:
bits 0..6 reserved = 0
bit 7 tombstoned被墓碑标记的记录属于保留下来的历史,不会出现在索引中,也不能 成为存活关系的目标。源转换不会写入任何墓碑标记。
Repository 元数据是:
repository_meta:
bits 0..6 reserved = 0
bit 7 tombstoned
lifecycle_kind:
1 active
2 retiring
3 purged
namespace_ascii[2]:
[0x00, 0x00] empty
[x, 0x00] one-byte namespace
[x, y ] two-byte namespace
policy_flags:
bit 0 require_attestation
bit 1 require_tests
bit 2 require_lint
bit 3 require_security_scan
bit 4 require_license_scan
bit 5 require_ai_provenance
bit 6 require_code_review_summary
bit 7 docs_only_relaxed_checks每个 x 或 y 都是一个非零的 ASCII 字母数字、_ 或 - 字节。 [0x00, y]、内嵌的零字节、控制/非 ASCII 字节,以及长度超过 两字节的输入,都是非法的。大小写和字节都是精确的;读取方不做任何 裁剪、折叠或 Unicode 归一化。逻辑上的命名空间长度 由尾部的零推导得出,绝不存储。
第 0 到第 6 位是该 Repository 的默认要求。当第 7 位置位、 且有效的内容类别是 docs_only 时,测试、lint、安全扫描和 许可证扫描全都不做要求;attestation、AI 溯源和代码评审 摘要保持它们的默认位。逻辑上的策略 ID 和版本固定 为 prototype 和 1,不做持久化。暴露 Repository 策略 JSON 的 API 会从 policy_flags 合成出规范的逻辑对象。
每一个未被墓碑标记的 Repository 都要求有非空的名称、一个合法的 namespace_ascii[2] 值,以及一个 policy_flags 字节。名称重复是 合法的。 active -> retiring -> purged 和 retiring -> active 是仅有的生命周期 转换。purged 的 Repository 没有存活的 Worker Job,也不能成为 新 Job 的属主。已 purge 的身份、Repository payload 字节,以及保留下来的 Repository 本地 Job 历史,在离线压实之前都保持精确不变;它们 绝不会从另一个领域重建出来。
Worker Job 状态是:
state_kind:
1 queued
2 running
3 succeeded
4 failed
outcome_kind:
0 none
1 completed
2 skipped
3 attached
4 superseded
5 failed
error_kind:
0 none
1 retryable_execution
2 terminal_execution
3 lease_expired每一个存活的 Job 都存放在某个 active 或 retiring Repository 的数值权威 目录之下。queued 和 running 要求 outcome_kind = none;succeeded 要求 completed、skipped、attached 或 superseded;failed 要求 outcome_kind = failed。新入队的 Job 是 error_kind = none。排队重试或运行中重试可以保留 retryable_execution 或 lease_expired。succeeded 要求 error_kind = none;failed 要求 terminal_execution 或 lease_expired。
attached 和 superseded 只保留"这个 Job 为什么没有独立完成 成功工作"这一信息。它们不标识另一个 Job。去重时用到的任何 等价活跃 Job 或后来成功 Job 的身份,都属于运行期/投影 数据,不是 Worker Job 权威。
lease 凭据不是 Binary DB 权威。认领时服务端会生成一个 密码学随机的 16 字节 lease_token,把存活条目保存在 内存中,并把它镜像到一个用于崩溃恢复的临时 Binary,该 Binary 位于所有 已激活的全局或 Repository 权威根之外。运行期条目以 (repository_index, worker_job_index, attempt_count) 为键,携带令牌、 最近一次心跳时间、过期时间和撕裂写检测。只有当定长的 Job 处于 running、其 attempt_count 精确匹配、 出示的令牌精确匹配,且运行期过期时间尚未到达时,它才有效。
那个临时 Binary 是一次性的运行期副本,不是 layout-1 的文件 家族。它被排除在权威清单、转换输出、Remote Binary 传输、离线压实和领域恢复之外。它不能把 排队中的 Job 变成运行中、不能修复 worker_job.bin,也不能提供 Job 身份。条目缺失、 损坏、陈旧、不匹配或过期,都会让 lease 失效;在 Repository 队列锁之下,服务端会按其尝试预算把该 Job 重新入队或判定为 终态失败,清除 locked_at_s,并记录 lease_expired。只有在运行中的 Job 记录和运行期镜像都已持久化之后, 认领才会返回令牌。心跳、完成和失败在持有队列锁期间都要求 同一个令牌。Worker 身份属于诊断用的运行期 数据,既不存进 Worker Job 权威,也不存进 lease 令牌。
这里未分配的每一个数值状态、结果或错误值都是保留的, 会 fail closed。完整的成功结果对象归属于它们所变更的领域记录。 完整的失败消息归属于被排除在外的追踪/日志或诊断 投影边界。两者都不是 Worker Job 权威。
运行期变更与恢复边界#
这些全局和以 Repository 为作用域的运行期家族,不增加任何权威的 WAL、journal、事务 ID 或通用 generation 记录。锁文件、 临时替换文件和 fsync 记账都属于运行期文件系统 元数据,不是 .bin 权威。如果一次变更需要多个领域 锁,写入方先获取服务端全局注册表锁,然后按 repository_index 升序获取各个 Repository 本地队列锁,并按相反顺序 释放:
<server-global-registry-root>/01-registry.lock
<server-repository-authority-parent>/<repository_index>/worker-queue.lock获取锁时会拒绝符号链接,也会拒绝归属于另一个已激活根的锁。 需要检查或墓碑标记 Job 的 Repository 生命周期转换,会按声明的顺序获取 注册表锁和它自己的 Repository 队列锁。Job 变更只获取其所属 Repository 的队列锁。那把本地锁负责给 Worker Job 定长家族、非权威的运行期 lease 副本、两个本地 Job 索引,以及每一次 Patchset ci_worker_job_index_plus1 或紧凑 CI 替换排序。读取方在解析定长记录期间持有 对应的共享锁;写入方则一直独占持有到领域提交点。
对于追加式创建,会先追加完整的定长明细记录并 fsync。 定长记录就是提交点。可重建索引最后写。 已提交记录的物理序号成为它永久的 worker_job_index;不存在独立的分配器、序列头、预留记录、 payload 追加或可复用的未提交身份。
覆写沿用既有的 v0 事务层契约:把 完整的前像保留在非权威的恢复存储中,写入一条完整的 定长记录(保留字节置零),fsync,然后丢弃前像。 除非那次替换抵达了它的领域提交点,否则恢复会还原完整的前像。同一条定长记录内部的字段绝不会被独立 观测到。
其他的领域提交规则是:
- Repository 创建会在下一个从未使用过的
repository_index上暂存并 fsync 真实的数值权威目录,然后追加 Repository 记录 并在repository.bin上提交。被打断的、提交前的目录属于 不可达的暂存内容,绝不会按名称被激活。策略标志位、命名空间 字节、生命周期和更新时间的变更,都通过替换完整的 Repository 记录来提交。 - Job 创建从已提交记录的完整计数推导出下一个
worker_job_index, 并在所属数值 Repository 权威内部的worker_job.bin上提交。 非patchset.ci的 Job 到此就完成了。对于patchset.ci,同一次持有队列锁的变更接着会替换完整的 所属 Patchset 记录,以选中那个新的worker_job_index;那次 Patchset 替换就是关系的提交点。在此之前被打断,可能 留下一个合法但未被选中的 Job,它无法提供紧凑 CI 证据。 - 认领会递增
attempt_count,创建新的运行期 lease 条目, 写入state_kind = running和locked_at_s,并通过替换完整的 Job 记录来提交权威。提交前的运行期条目会被忽略, 除非定长的状态和尝试计数与它精确匹配。已提交的 运行中 Job 若缺少其有效的运行期条目,即为 lease 丢失,会按尝试规则重新入队或 判定失败。只有在两次写入都完成 fsync 之后才会返回令牌。心跳会精确比对令牌和尝试计数,持久化 更新后的运行期心跳/过期时间,并用同一个更新后的locked_at_s替换完整的定长 Job 记录。失败会让运行期条目失效、 清除locked_at_s、记录定长的错误类别,并选择 重新入队或写入终态失败结果。 - 一次成功的执行会先把结果提交到既有的所属领域 权威,然后再把 Job 替换为
state_kind = succeeded、写入其 定长结果和locked_at_s = 0,最后让运行期 lease 条目失效。因此被选中的patchset.ci紧凑证据,是在那个 Job 仍处于选中状态、 且 Job 成功标记尚未写入之前,通过整条 Patchset 替换提交的。领域优先的完成过程若被打断,会以幂等方式重试。attached和superseded只提交那个终态结果; 不会持久化第二个 Job 身份。陈旧的运行期条目无法让 终态 Job 复活。队列索引和调度视图都是可修复的。 - Repository 转为
purged时,会先在保留紧凑 CI 证据的前提下,通过整条记录替换清除每一个 Patchset 的ci_worker_job_index_plus1,然后墓碑标记它拥有的每一个存活 Job,最后 提交完整的 Repository 生命周期替换。在最后那次替换之前被打断, 会同时还原 Patchset 和 Job 的前像。
恢复过程首先校验头部、记录的精确整除关系、保留位为零、 那四个固定的 Repository 槽位、Repository 的 UTF-8、命名空间字节、策略 标志位、时间戳、结果、错误,以及所有定长引用不变式。它会 在打开每一个以仓库为作用域的 Worker Job 权威之前,精确校验数值 Repository 目录闭包。随后它会构建完整的源索引 图,并拒绝任何指向不存在、已被墓碑标记或属主错误 记录的存活引用;拒绝互相冲突的存活非空命名空间;也拒绝任何非零的 Patchset Job 定位符——如果它无法精确校验出一个同根、job_kind = 7、其 patchset_index_plus1 选中该 Patchset 的 Job。重复的 Repository 名称, 以及不同 Repository 根中数值相同的本地 Worker Job 索引,绝不构成 恢复冲突。
只有在权威校验通过之后,恢复才可以重建注册表命名空间 索引和各个 Repository 的本地 Job 索引。可变的 Repository 元数据、 Patchset 选择和 Job 状态,绝不会从后面的行或某个 索引推断出来。随后恢复会加载运行期 lease 副本;副本缺失就当作 空副本。每个条目都必须与某一个运行中的 Job 及其尝试 计数精确匹配,且未过期;否则该条目会被丢弃,且该 Job 按 lease 丢失规则处理。 不完整的尾部定长记录属于损坏,除非事务层 能证明它是唯一一次被打断的追加。中间位置的畸形字节绝不会 被截断修复。
运行期容量与压实#
每一个定长家族和可重建索引,其存活目标索引都以 u32::MAX - 1 为上限,因为非零的 *_index_plus1 必须保持可表示。 这个定长家族上限对每个 Repository 里的 worker_job.bin 独立生效; 不存在任何持久化的全局 Job 家族,会额外施加一个 安装范围的 Job 数量上限。由于 Worker Job 的墓碑标记会保留其 主键槽位,它的容量检查用的是已提交记录总数,而不是存活 Job 数。直接的 u32 源值和计数在写入前都会预检。Repository 的 payload 偏移和文件大小必须放得进 u64;每一处 offset + length 计算都使用带检查的算术。
离线压实会写出一个新的、非激活的注册表根和 Repository generation,保留每一个确切的 repository_index 和 worker_job_index、Repository 名称、命名空间字节、策略标志位、状态、 kind、定长引用、结果、错误和时间。它绝不会重排、 移除或重新编号 repository.bin 或 worker_job.bin 的记录; Repository 和 Worker Job 的墓碑标记都保留其槽位,之后的任何身份 都不得复用它们。其他物理家族只有在改写完它们完整的引用和索引闭包之后,才可以被稠密重编号。压实会重建 那两个本地 Job 索引,同时不改变 Job 记录顺序和 Patchset 定位符。 它不会读取、复制或创建那个临时的运行期 lease 副本, 并且会在原子激活之前校验完整的目标。
改变定长宽度、状态分配、payload 语法、文件家族、 主键规则、转换源边界或容量,都需要一次新的 布局转换,以及兼容的读写方。layout_id 没有变化 并不构成做出这些改变的许可。