edit | blame | history | raw

慧博 10 分钟快速采集工具开发方案 V004

创建人员:dev.developer.ana.cai
文件职责:append-only 关闭 V003 复审仅余的公开 terminal/manifest wire 和 quota event-family/transaction 两项。
predecessor:V003=28781/a6d46d9ffc8081a94ea76436a6201c9cf67b1f9db5d1f70692e2d294bfba6187
predecessor review:AUDIT-DEV-ANA-HIBOR-FAST-COLLECTION-DESIGN-V003=HOLD/2/2
继承:V003 已关闭 BLOCK-02;V002 已关闭 BLOCK-03/04/06;V001/V002/V003 的 Job、deadline、candidate、atomic publish、package、performance、quota lock/read-fold/replay 和全部禁止边界继续有效。V004 对 terminal nested item、manifest literal/content type 和 quota event identity/调用顺序拥有后继优先级。

1. 公开 terminal 唯一映射

1.1 CapabilityStatus 封闭枚举

ACCEPTED|ACCEPTED_PARTIAL_QUOTA|QUEUED_NEXT_DAY|PARTIAL_QUOTA_STOP|TIME_BUDGET_STOP|BLOCKED_INPUT|BLOCKED_ACCESS_CONTROL|BLOCKED_ENVIRONMENT|BLOCKED_AMBIGUOUS_MAPPING|VALIDATION_FAILED|INTERNAL_ERROR|STATE_UNCERTAIN

接单阶段的 ACCEPTED_PARTIAL_QUOTA/QUEUED_NEXT_DAY 由采集角色在 CLI 运行前/外部安排时使用;CLI 的 report_collection_terminal 不反向伪造接单消息。CLI 内部 TerminalStatus 到公开 capability_status 的唯一总映射如下,不允许“最接近”、default 分支或运行时选择:

precedence internal TerminalStatus public capability_status exit
1 STATE_UNCERTAIN STATE_UNCERTAIN 27
2 BLOCKED_ACCESS_CONTROL BLOCKED_ACCESS_CONTROL 13
3 TIME_BUDGET_STOP TIME_BUDGET_STOP 10
4 PARTIAL_QUOTA_STOP PARTIAL_QUOTA_STOP 11
5 BLOCKED_INPUT BLOCKED_INPUT 12
6 BLOCKED_ENVIRONMENT BLOCKED_ENVIRONMENT 14
7 BLOCKED_AMBIGUOUS_MAPPING BLOCKED_AMBIGUOUS_MAPPING 15
8 VALIDATION_FAILED VALIDATION_FAILED 16
9 INTERNAL_ERROR INTERNAL_ERROR 20
10 PARTIAL_SUCCESS ACCEPTED 2
11 SUCCESS ACCEPTED 0

PARTIAL_SUCCESSSUCCESS 虽共享公开既有值 ACCEPTED,消费者仍由 top-level status 和 exit 2/0 唯一识别;同一 internal status 永远只有一个公开值。若多个 stop 同时出现,先按 precedence 选唯一 internal status,再按本表映射一次。

1.2 TerminalItem V004(固定 26 键)

item_id,slot_id,candidate_id,report_identity,state,status,stop_code,trigger_attempted,triggered,quota_state,quota_reservation_id,quota_terminal_event_id,quota_artifact_event_id,title,institution,report_date,analysts,page_count,bytes,sha256,source_cache_path,source_file_name,final_path,manifest_row_id,reused_without_new_trigger,error_or_note

类型:前 13 个 identity/state 字段为 str|Nonetrigger_attempted/triggered/reused_without_new_trigger 为 BOOL);analysts=tuple[str,...>|nullpage_count/bytes=int|nullsha256=Hash|null;路径为 str|nullstatus=SUCCESS|DUPLICATE|FAILED|STOPPEDstate=P00_INIT..P10_CLOSEDquota_state=NONE|RESERVED|CONFIRMED|UNCERTAIN|RELEASED

变体约束:

  1. SUCCESS:candidate/report/title/institution/date/analysts/page/bytes/hash/cache/final/manifest 全非空;triggered=true(或 resume 时 reused=true、triggered=false);quota terminal/artifact event 非空;stop_code=null;error=NONE
  2. DUPLICATE:report identity、bytes/hash/final/manifest 非空;quota_state=RELEASED|CONFIRMED 取决于是否已触发,必须有对应 terminal event;stop_code=DUPLICATE_EXISTING_ARTIFACT,error 说明已有证据。V004 将 DUPLICATE_EXISTING_ARTIFACT 追加到 V003 ErrorCode 封闭枚举。
  3. FAILED:只记录实际可知值;status/stop_code/error 非空,不得填造 title/PDF/hash。若 trigger 已 confirmed/uncertain,quota terminal 与 artifact failure event 非空。
  4. STOPPED:由高优先级 terminal 导致,未进入步骤的字段 null;state 和 trigger/quota 交叉字段必须反映实际 package row。

TerminalRecord.items 的类型固定为 tuple[TerminalItem,...],按 item_id ordinal 排序;不得“另含”未声明字段或按成功/失败追加额外键。公开 PDF 明细完全由 TerminalItem 固定字段承载。

2. Manifest literal 与 evidence-based content type

V003 48 列顺序不变,仅冻结三个值域:

  • source_site 固定精确字面量:慧博 APP(安卓模拟器本地缓存)
  • http_status:APP/cache 来源固定 NOT_APPLICABLE_APP_CACHE;若尚未证明来源是该 cache,填 UNKNOWN
  • content_type 只按已取得证据赋值:pdf_magic_valid=true 时为 application/pdf;存在普通本地文件且 magic 已验证 false 时为 application/octet-stream;文件未出现、不可读、magic 未执行或事实未知时为 UNKNOWN。失败/STOP 行不得无条件声称 PDF。

download_status 的固定值为 SUCCESS|DUPLICATE|FAILED|STOPPEDstatus 必须相同。无正式副本时 base file_name,relative_path,bytes,sha256 为空字段,error_or_note 非空;规范允许未知的 title/publisher/report_date/source_url 使用 UNKNOWN,不得用 null 破坏 CSV 列宽。

3. Quota event-family identity V004

3.1 QuotaEvent CSV V004(固定 34 列)

V003 33 列在公开最小字段 note 后新增 event_family,其余列顺序/语义不变;前 20 列继续与上游完全一致:

quota_date,timezone,platform_limit,automation_target,automation_hard_stop,reserved_buffer,task_id,requester_role,handoff_id,event_at,report_identity,event_type,confirmed_consumed,uncertain_consumed,active_reservation_delta,success_unique_pdf_delta,duplicate_or_failed_delta,cumulative_consumed,safe_available_after,note

后 14 列为:

event_family,schema_version,event_id,idempotency_key,event_seq,run_id,slot_id,reservation_id,ref_event_id,evidence_ref,external_baseline_floor,confirmed_delta,uncertain_delta,app_total_consumed

event_family=BASELINE|APP_OBSERVATION|RESERVATION|QUOTA_TERMINAL|ARTIFACT_TERMINAL。每个事件类型到 family 唯一:

  • BASELINE_ESTIMATE/CORRECTION_RAISE→BASELINE;
  • APP_RECONCILE→APP_OBSERVATION;
  • RESERVE→RESERVATION;
  • CONSUME_CONFIRMED/CONSUME_UNCERTAIN/RELEASE→QUOTA_TERMINAL;
  • ARTIFACT_SUCCESS/ARTIFACT_DUPLICATE_OR_FAILED→ARTIFACT_TERMINAL。

3.2 family-scoped idempotency preimage

所有 preimage 用 UTF-8、| 分隔、字段不得含 |idempotency_key=sha256(preimage)event_id=sha256('HIBOR-QUOTA-EVENT-V004|'+idempotency_key+'|'+event_type)。event_seq 只用于 ledger ordinal,不进入 event_id;replay 先按 family+idempotency_key 查找,再直接返回既有整行,绝不以新 event_at 构造冲突行。

family idempotency preimage
BASELINE estimate quota_date|BASELINE|BASELINE_ESTIMATE|evidence_ref|external_baseline_floor
BASELINE correction quota_date|BASELINE|CORRECTION_RAISE|ref_event_id_or_NONE|evidence_ref|external_baseline_floor
APP_OBSERVATION quota_date|APP_OBSERVATION|observation_id
RESERVATION quota_date|RESERVATION|task_id|handoff_id|slot_id|report_identity
QUOTA_TERMINAL quota_date|QUOTA_TERMINAL|reservation_id
ARTIFACT_TERMINAL quota_date|ARTIFACT_TERMINAL|task_id|handoff_id|run_id|slot_id|report_identity

observation_id=sha256(device_serial|quota_date|captured_at_utc|visible_remaining|ui_snapshot_fingerprint),这些字段来自一次已完成只读 UI snapshot;APP row 的 report_identity='__APP_TOTAL__'、slot_id 为空、evidence_ref=APP-OBS:<observation_id>。同 reservation 发生 confirm/uncertain/release 竞争时共享 QUOTA_TERMINAL key,首个合法 committed row 胜出;后续同整行 replay,其他 event_type 为冲突 STOP。artifact 的 run_id 允许失败后在新 run 做 postprocess,但 reducer 的 success_unique 以 report_identity 去重。

reservation_id 固定等于 RESERVE 行的 event_id;QUOTA_TERMINAL.ref_event_id 必须等于该 reservation_id;ARTIFACT_TERMINAL.ref_event_id 必须等于该 report 的 QUOTA_TERMINAL event_id。不同 family 的同 report 事件因此既可串联又不会共享 idempotency key。

3.3 Quota API 后继签名

V004 替换 V003 中带 app_visible_remaining 的 quota API:

def snapshot(ctx: RunContext) -> QuotaSnapshot
def observe_app_remaining(ctx: RunContext, observation: AppQuotaObservation) -> QuotaEvent
def reserve(ctx: RunContext, report_identity: str, slot_id: str,
            observation_event_id: str | None) -> ReservationResult
def confirm(ctx: RunContext, reservation: ReservationResult,
            trigger: TriggerReceipt) -> QuotaEvent
def mark_uncertain(ctx: RunContext, reservation: ReservationResult,
                   trigger: TriggerReceipt, reason: ErrorCode) -> QuotaEvent
def release(ctx: RunContext, reservation: ReservationResult,
            reason: ErrorCode) -> QuotaEvent

AppQuotaObservation 固定 6 键:observation_id,device_serial,captured_at_utc,visible_remaining,app_total_consumed,ui_snapshot_fingerprint;visible 0..30,app_total=30-visible,observation_id 必须按上式复算。

4. APP observation→reserve:只用独立已提交事务

每个 quota API 调用在锁内最多追加一行;V004 明确选择“先独立提交 reconcile,再独立 reserve”,禁止一锁两行和隐式多行事务:

  1. UI 未取得可靠剩余量:不调用 observe,直接 reserve(...,observation_event_id=None),只按已有 ledger 保守 fold。
  2. UI 取得可靠剩余量:调用 observe_app_remaining,在一个临界区完成 read→validate→fold→append APP_RECONCILE→flush/fsync→reopen/fold;返回已 committed event。
  3. CLI 只有在步骤 2 返回 committed/replayed event_id 后,才调用 reserve 并传该 id。reserve 在新临界区重读全 ledger,验证 observation event 存在、family/日期/device evidence 正确,fold 后再 check safe/append 一行。
  4. crash 在 reconcile 前:无行、无 reservation;crash 在 reconcile 后/reserve 前:只有更保守 APP 总量,无 active reservation,安全可重跑;crash 在 reserve append 前:无 reservation;append 后:active reservation 保守占用。不存在“第一行未 durable 却写第二行”。
  5. 任一 append/fsync/reopen mismatch 返回 STATE_UNCERTAIN/27;不做下一 API、不 rollback/删行。已 durable reconcile 可重放;已 durable reservation 由后续同 reservation terminal 收口。

并发时两个进程可先各提交不同 observation;reducer 取历史 APP max。随后 reserve 各自重新 fold,最后 safe slot 仍只有一个成功。reserve 引用较早但同日合法 observation 仍使用全 ledger 最新更保守 max,不使用该行的旧余额。

5. 强制测试与当前 gate

新增测试:

  1. 11 个 TerminalStatus 全映射、所有 precedence pair、未知 enum 拒绝、SUCCESS/PARTIAL 同公开值但 top-level 可区分;
  2. TerminalItem SUCCESS/DUPLICATE/FAILED/STOPPED 的 26 键、null/enum/交叉字段与 extra-key 拒绝;
  3. manifest 精确 source_site、PDF/octet-stream/UNKNOWN 三种 content type、失败/unknown 行不制造 PDF 断言;
  4. 五个 event family 的 preimage/event_id/replay/conflict;同 report reservation/quota-terminal/artifact-terminal 三个 key 必须互不相同;APP observation 无 slot/report 仍可唯一重放;
  5. reconcile 前后 crash、reconcile committed 后 reserve、reserve 前后 crash、两进程最后 safe slot、同 reservation confirm/uncertain race、artifact fail→新 run success、同 run replay;
  6. V003 七个 reducer 向量保持,且每个公开 quota snapshot 字段与 34 列行一致。

当前状态:PENDING_INDEPENDENT_DESIGN_REREVIEW。V004 PASS 前仍不创建源码/测试/额度账本,不运行 Python/ADB/APP/dry-run,不触发下载;新增 trigger=0