from __future__ import annotations from dataclasses import dataclass, field, replace from datetime import datetime, timezone import csv import hashlib import json from pathlib import Path import re import time from typing import Any, Callable, Mapping from zoneinfo import ZoneInfo from .archive import publish_no_replace, sha256_file, validate_pdf from .budget import Budget from .cache import CacheWatcher from .items import terminal_item from .manifests import (MANIFEST_COLUMNS, utc_now, write_bytes_create_new, write_json_create_new, write_manifest_create_new) from .models import ( Candidate, ContractError, ErrorCode, EvidenceState, ItemStatus, PackageState, QuotaState, TaskSpec, TerminalReceipt, TerminalStatus, ) from .quota import AppQuotaObservation, QuotaLedger, QuotaSnapshot, Reservation from .selection import score_candidate from .terminal import TerminalWriter, build_terminal SOURCE_SITE = "慧博 APP(安卓模拟器本地缓存)" @dataclass class StageTimings: rows: list[dict[str, Any]] = field(default_factory=list) def record(self, stage: str, start_ns: int, end_ns: int, status: str) -> None: self.rows.append({ "stage": stage, "started_monotonic_ns": start_ns, "ended_monotonic_ns": end_ns, "elapsed_ms": (end_ns - start_ns) // 1_000_000, "status": status, }) def _identity(candidate: Candidate) -> str: text = "|".join((candidate.title, candidate.institution or "", candidate.report_date or "")) return hashlib.sha256(text.encode("utf-8")).hexdigest() def _safe_part(value: str, maximum: int = 60) -> str: value = re.sub(r"[<>:\"/\\|?*\x00-\x1f]", "_", value).strip(" ._") return (value or "UNKNOWN")[:maximum] def _manifest_row(**values: Any) -> dict[str, Any]: return {key: values.get(key) for key in MANIFEST_COLUMNS} class HiborWorkflow: def __init__(self, spec: TaskSpec, *, budget: Budget | None = None, monotonic_ns: Callable[[], int] = time.monotonic_ns, utc_now_provider: Callable[[], datetime] | None = None): self.spec = spec self.budget = budget or Budget( spec.total_budget_ms, spec.close_reserve_ms, observed_at_utc=spec.observed_at_utc, batch_increment_ms=spec.batch_increment_budget_ms, ) self._monotonic_ns = monotonic_ns self._utc_now = utc_now_provider or (lambda: datetime.now(timezone.utc)) self.timings = StageTimings() self.item_windows: list[dict[str, Any]] = [] self._open_item_windows: dict[int, dict[str, Any]] = {} self.started_at = self._utc_now().isoformat().replace("+00:00", "Z") def dry_run(self) -> dict[str, Any]: """Read-only environment check. It never starts ADB or creates a quota ledger.""" start = self._monotonic_ns() tool_checks = { "adb_executable_exists": Path(self.spec.adb_executable).is_file(), "pdfinfo_executable_exists": Path(self.spec.pdfinfo_executable).is_file(), "output_parent_exists": self.spec.output_path.parent.is_dir(), "quota_ledger_exists": self.spec.quota_path.is_file(), } status = "PASS" if all(v for k, v in tool_checks.items() if k != "quota_ledger_exists") else "BLOCKED" self.timings.record("dry_run", start, self._monotonic_ns(), status) quota = self._quota_snapshot_readonly() target = self.spec.output_path / "terminal" / f"{self.spec.task_id}.json" receipt = TerminalReceipt(target, None, False, False, EvidenceState.N, False, False, True, None, None, None, PackageState.P00_INIT) base = self._base_terminal(quota, run_id=f"DRY-{self.spec.task_id}") if status == "PASS": return build_terminal(base, receipt, status=TerminalStatus.SUCCESS, stop_code=None) return build_terminal(base, receipt, status=TerminalStatus.BLOCKED_ENVIRONMENT, stop_code=ErrorCode.PROCESS_START_FAILED, blocker="LOCAL_TOOL_PREFLIGHT") def discover(self, *, adb: Any, ui: Any, execute: bool = False) -> dict[str, Any]: """Discover and verify exact report identities without quota or download actions. This entry may navigate public APP result/detail screens, but it never calls the download trigger, creates a quota ledger, pulls cache files, or publishes artifacts. Its canonical stdout is the input to the separately frozen performance plan. """ if execute is not True: raise ContractError(ErrorCode.TASK_SPEC_INVALID, "execute", "explicit True required") if self.spec.mode != "discover": raise ContractError(ErrorCode.TASK_SPEC_INVALID, "mode", "discover required") if hasattr(adb, "set_timeout_provider"): adb.set_timeout_provider( lambda operation, maximum: self.budget.require("discovery", operation, maximum) ) started_ns = self._monotonic_ns() self.budget.checkpoint("discovery", "adb_preflight") adb.preflight() self.budget.checkpoint("discovery", "search") ui.search(self.spec.query) scan = ui.scan( query=self.spec.query, quantity=self.spec.quantity, aliases=tuple(self.spec.aliases), institutions=tuple(self.spec.institutions), minimum_pages=self.spec.minimum_pages, min_screens=self.spec.min_screens, normal_max_screens=self.spec.normal_max_screens, hard_max_screens=self.spec.hard_max_screens, hard_max_candidates=self.spec.hard_max_candidates, ) candidates: list[dict[str, Any]] = [] seen: set[str] = set() for candidate in scan.candidates: if len(candidates) >= self.spec.quantity: break self.budget.checkpoint("discovery", "detail") ui.search(self.spec.query) detailed = ui.open_detail(candidate) detailed = replace(detailed, score=score_candidate( detailed, query=self.spec.query, aliases=tuple(self.spec.aliases), institutions=tuple(self.spec.institutions), minimum_pages=self.spec.minimum_pages, )) self._enforce_hard_filters(detailed) identity = _identity(detailed) if identity in seen: continue seen.add(identity) candidates.append({ "report_identity": identity, "title": detailed.title, "institution": detailed.institution, "report_date": detailed.report_date, "page_count": detailed.page_count, }) if len(candidates) < self.spec.quantity: raise ContractError(ErrorCode.CANDIDATE_REJECTED, "discovery", "insufficient exact candidates") ended_ns = self._monotonic_ns() return { "schema_version": "HIBOR_FAST_DISCOVERY_V001", "task_id": self.spec.task_id, "handoff_id": self.spec.handoff_id, "performance_plan_id": self.spec.performance_plan_id, "performance_slot_id": self.spec.performance_slot_id, "query": self.spec.query, "observed_at_utc": self.spec.observed_at_utc, "completed_at_utc": self._utc_now().isoformat().replace("+00:00", "Z"), "started_monotonic_ns": started_ns, "ended_monotonic_ns": ended_ns, "screens_scanned": scan.screens_scanned, "candidate_count": len(candidates), "candidates": candidates, "quota_ledger_touched": False, "trigger_attempted": False, } def collect(self, *, adb: Any, ui: Any, execute: bool = False, synthetic: bool = False, watcher_factory: Callable[[Callable[[], Any]], CacheWatcher] = CacheWatcher) -> dict[str, Any]: """Execute one bounded collection run using injected ADB/UI adapters. ``execute`` is deliberately mandatory so importing this module cannot trigger APP work. ``synthetic`` skips the external ``pdfinfo`` process and is accepted only by local tests. """ if execute is not True: raise ContractError(ErrorCode.TASK_SPEC_INVALID, "execute", "explicit True required") if self.spec.mode not in {"collect-one", "batch", "resume-postprocess"}: raise ContractError(ErrorCode.TASK_SPEC_INVALID, "mode", "collect-one, batch or resume-postprocess required") run_id = self._run_id() run_root = self.spec.output_path / run_id screenshot_root = run_root / "screenshots" staging_root = run_root / "staging" manifest_path = run_root / "manifest.csv" delivery_path = run_root / "delivery.md" timing_path = run_root / "timing.json" terminal_path = run_root / "report_collection_terminal.json" items: list[dict[str, Any]] = [] manifest_rows: list[dict[str, Any]] = [] triggered = 0 stop_code: ErrorCode | None = None blocker: str | None = None terminal_status = TerminalStatus.SUCCESS quota: QuotaLedger | None = None local_date = datetime.now(ZoneInfo("Asia/Shanghai")).date().isoformat() known_floor = 3 if local_date == "2026-07-29" else 0 quota_snapshot = QuotaSnapshot(local_date, 0, 0, 0, known_floor, 0, known_floor, 25 - known_floor, 0) package_state = PackageState.P00_INIT manifest_verified = False delivery_verified = False timing_verified = False try: # Bind the first SLA window before replay/reuse discovery so even a # zero-trigger result must close within observed_at+600s. self._begin_item(1) # A fully persisted request is an immutable replay. Returning it before # ADB, APP or quota access is the primary duplicate-trigger guard. self.budget.checkpoint("replay", "terminal_read") replay = TerminalWriter().read_valid( terminal_path, task_id=self.spec.task_id, handoff_id=self.spec.handoff_id, run_id=run_id, ) if replay is not None: return replay quota_snapshot = self._quota_snapshot_readonly() if hasattr(adb, "set_timeout_provider"): adb.set_timeout_provider( lambda operation, maximum: self.budget.require("external", operation, maximum) ) reuse_rows = self._discover_project_reuse(synthetic=synthetic) if len(reuse_rows) >= self.spec.quantity: self._close_reuse_item_windows(self.spec.quantity) return self._close_project_reuse( run_id=run_id, run_root=run_root, manifest_path=manifest_path, delivery_path=delivery_path, timing_path=timing_path, terminal_path=terminal_path, rows=reuse_rows[:self.spec.quantity], ) if reuse_rows: reuse_manifest, reuse_items = self._build_reuse_payload(run_id, reuse_rows) manifest_rows.extend(reuse_manifest) items.extend(reuse_items) self._close_reuse_item_windows(len(reuse_items)) if self.spec.mode == "resume-postprocess": cache_rows = self._materialize_cache_reuse( adb=adb, run_root=run_root, synthetic=synthetic, ) if len(cache_rows) < self.spec.quantity: raise ContractError(ErrorCode.CANDIDATE_REJECTED, "resume_items", "insufficient verified cache evidence") return self._close_project_reuse( run_id=run_id, run_root=run_root, manifest_path=manifest_path, delivery_path=delivery_path, timing_path=timing_path, terminal_path=terminal_path, rows=cache_rows[:self.spec.quantity], ) except ContractError as exc: return self._preflight_terminal(run_id, terminal_path, quota_snapshot, exc) try: stage = self._monotonic_ns() if self.budget.work_deadline_reached: raise ContractError(ErrorCode.DEADLINE_EXPIRED, "preflight", "work deadline") self.budget.checkpoint("preflight", "adb_preflight") adb.preflight() self.budget.checkpoint("preflight", "adb_preflight_complete") if not synthetic: if not Path(self.spec.pdfinfo_executable).is_file(): raise ContractError(ErrorCode.PROCESS_START_FAILED, "pdfinfo", "executable absent") quota_date = datetime.now(ZoneInfo("Asia/Shanghai")).date().isoformat() quota = QuotaLedger( self.spec.quota_path, quota_date=quota_date, timeout_provider=lambda operation, maximum: self.budget.require( "quota", operation, maximum, close=self.budget.work_deadline_reached), ) known_floor = 3 if quota_date == "2026-07-29" else 0 self.budget.checkpoint("quota", "initialize") quota_snapshot = quota.initialize( task_id=self.spec.task_id, requester_role=self.spec.requester, handoff_id=self.spec.handoff_id, known_floor=known_floor, evidence_ref="KNOWN-2026-07-29-CHINA-DUTYFREE-3" if known_floor else "NO_KNOWN_EXTERNAL_BASELINE", ) adb.screenshot(screenshot_root / "01-start.png") self.timings.record("preflight", stage, self._monotonic_ns(), "PASS") stage = self._monotonic_ns() ui.search(self.spec.query) app_observation_event_id = "" if hasattr(ui, "observe_app_remaining"): observation = ui.observe_app_remaining(quota_date) if observation is not None: if not isinstance(observation, AppQuotaObservation): raise ContractError(ErrorCode.QUOTA_LEDGER_INVALID, "app_observation", "type") self.budget.checkpoint("quota", "app_reconcile") app_event = quota.observe_app_remaining( task_id=self.spec.task_id, requester_role=self.spec.requester, handoff_id=self.spec.handoff_id, observation=observation, ) app_observation_event_id = app_event["event_id"] quota_snapshot = quota.snapshot() needed = self.spec.quantity - len(items) scan = ui.scan( query=self.spec.query, quantity=needed, aliases=tuple(self.spec.aliases), institutions=tuple(self.spec.institutions), minimum_pages=self.spec.minimum_pages, min_screens=self.spec.min_screens, normal_max_screens=self.spec.normal_max_screens, hard_max_screens=self.spec.hard_max_screens, hard_max_candidates=self.spec.hard_max_candidates, ) adb.screenshot(screenshot_root / "02-results.png") expected_slice = self.spec.expected_reports[len(items):] candidates = [] for expected_report in expected_slice: matches = [candidate for candidate in scan.candidates if candidate.title == expected_report["title"]] if len(matches) != 1: if candidates: break raise ContractError(ErrorCode.DETAIL_RESULT_MISMATCH, "expected_report.title", "not uniquely present in discovery scan") candidates.append(matches[0]) if not candidates: raise ContractError(ErrorCode.CANDIDATE_REJECTED, "scan", "no candidate") self.timings.record("ui_scan", stage, self._monotonic_ns(), scan.status) reuse_count = len(items) for new_offset, candidate in enumerate(candidates, 1): index = reuse_count + new_offset self._begin_item(index) if self.budget.work_deadline_reached: stop_code = ErrorCode.DEADLINE_EXPIRED terminal_status = TerminalStatus.TIME_BUDGET_STOP blocker = "WORK_DEADLINE_REACHED" break slot_id = f"SLOT-{index:03d}" item_id = f"ITEM-{index:03d}" reservation: Reservation | None = None quota_terminal: dict[str, str] | None = None action_attempted = False trigger_confirmed = False item_terminal_recorded = False detailed = candidate report_identity: str | None = None source_state = PackageState.P00_INIT match = None remote_sha: str | None = None published = None artifact_event: dict[str, str] | None = None try: stage = self._monotonic_ns() expected_report = self.spec.expected_reports[index - 1] if new_offset > 1: ui.search(self.spec.query) scan = ui.scan( query=self.spec.query, quantity=new_offset, aliases=tuple(self.spec.aliases), institutions=tuple(self.spec.institutions), minimum_pages=self.spec.minimum_pages, min_screens=self.spec.min_screens, normal_max_screens=self.spec.normal_max_screens, hard_max_screens=self.spec.hard_max_screens, hard_max_candidates=self.spec.hard_max_candidates, ) matches = [row for row in scan.candidates if row.title == expected_report["title"]] if len(matches) != 1: raise ContractError(ErrorCode.DETAIL_RESULT_MISMATCH, "expected_report.title", "not uniquely relocatable") candidate = matches[0] # The scanner leaves the viewport at its final page. Restart the query so # the UI adapter can deterministically relocate the exact title before tap. ui.search(self.spec.query) detailed = ui.open_detail(candidate) detailed = replace(detailed, score=score_candidate( detailed, query=self.spec.query, aliases=tuple(self.spec.aliases), institutions=tuple(self.spec.institutions), minimum_pages=self.spec.minimum_pages, )) report_identity = _identity(detailed) self._enforce_hard_filters(detailed) actual_report = { "report_identity": report_identity, "title": detailed.title, "institution": detailed.institution, "report_date": detailed.report_date, "page_count": detailed.page_count, } if actual_report != expected_report: raise ContractError(ErrorCode.DETAIL_RESULT_MISMATCH, "expected_report", "detail identity/fields drift") if any(item.get("report_identity") == report_identity for item in items): self.timings.record(f"item_{index}_detail", stage, self._monotonic_ns(), "SKIP_ALREADY_REUSED") item_terminal_recorded = True if not self._complete_item(index): raise ContractError(ErrorCode.DEADLINE_EXPIRED, f"item_{index}", "item_close") continue adb.screenshot(screenshot_root / f"{index + 2:02d}-detail.png") self.timings.record(f"item_{index}_detail", stage, self._monotonic_ns(), "PASS") self.budget.checkpoint(f"item_{index}", "quota_reserve") reservation = quota.reserve( task_id=self.spec.task_id, requester_role=self.spec.requester, handoff_id=self.spec.handoff_id, run_id=run_id, slot_id=slot_id, report_identity=report_identity, observation_event_id=app_observation_event_id, ) quota_snapshot = reservation.snapshot if not reservation.allowed: replay_block = reservation.replayed stop_code = ErrorCode.QUOTA_EXHAUSTED if replay_block: stop_code = ErrorCode.QUOTA_REPLAY_CONFLICT terminal_status = (self._status_for_error(stop_code, bool(items)) if replay_block else TerminalStatus.PARTIAL_QUOTA_STOP) blocker = "TERMINALLED_OR_ACTIVE_RESERVATION_REPLAY" if replay_block else "DAILY_SAFE_QUOTA_EXHAUSTED" item_terminal_recorded = True if not self._complete_item(index): raise ContractError(ErrorCode.DEADLINE_EXPIRED, f"item_{index}", "item_close") row = self._failure_manifest_row( run_id=run_id, item_id=item_id, slot_id=slot_id, candidate=detailed, report_identity=report_identity, code=stop_code, note=blocker, reservation=None, quota_terminal=None, artifact_event_id=None, action_attempted=False, ) manifest_rows.append(row) items.append(terminal_item({ "item_id": item_id, "slot_id": slot_id, "candidate_id": detailed.candidate_id, "report_identity": report_identity, "state": PackageState.P00_INIT.value, "status": ItemStatus.STOPPED.value, "stop_code": stop_code.value, "trigger_attempted": False, "triggered": False, "quota_state": QuotaState.NONE.value, "quota_reservation_id": None, "quota_terminal_event_id": None, "quota_artifact_event_id": None, "title": detailed.title, "institution": detailed.institution, "report_date": detailed.report_date, "analysts": list(detailed.analysts), "page_count": detailed.page_count, "bytes": None, "sha256": None, "source_cache_path": None, "source_file_name": None, "final_path": None, "manifest_row_id": row["row_id"], "reused_without_new_trigger": False, "error_or_note": blocker, })) break source_state = PackageState.P01_RESERVED watcher = watcher_factory(adb.list_cache) self.budget.checkpoint(f"item_{index}", "cache_baseline") baseline = watcher.baseline() stage = self._monotonic_ns() action_attempted = True ui.trigger_current_detail() source_state = PackageState.P02_TRIGGER_UNKNOWN remaining = self.budget.require(f"item_{index}", "cache_wait", 90_000) match = watcher.wait_for_unique_stable(baseline, timeout_ms=remaining) if match.status == "AMBIGUOUS": raise ContractError(ErrorCode.CACHE_AMBIGUOUS, "cache", ",".join(match.ambiguous_paths)) if match.status != "UNIQUE_STABLE" or match.remote is None: raise ContractError(ErrorCode.CACHE_TIMEOUT, "cache", "no stable new file") trigger_confirmed = True triggered += 1 source_state = PackageState.P03_CACHE_MATCHED self.budget.checkpoint(f"item_{index}", "quota_confirm") quota_terminal = dict(quota.terminal( reservation, event_type="CONSUME_CONFIRMED", task_id=self.spec.task_id, requester_role=self.spec.requester, handoff_id=self.spec.handoff_id, run_id=run_id, slot_id=slot_id, report_identity=report_identity, note="UNIQUE_STABLE_CACHE_FILE", )) self.timings.record(f"item_{index}_cache", stage, self._monotonic_ns(), "PASS") stage = self._monotonic_ns() remote_sha = adb.remote_sha256(match.remote.path) staging_path = staging_root / f"{item_id}.pdf" adb.pull(match.remote.path, staging_path, timeout_ms=self.budget.require(f"item_{index}", "adb_pull", 60_000)) source_state = PackageState.P04_STAGING_PARTIAL if staging_path.stat().st_size != match.remote.bytes: raise ContractError(ErrorCode.BYTE_COUNT_MISMATCH, "staging", "remote/local bytes") if sha256_file(staging_path, checkpoint=lambda: self.budget.checkpoint( f"item_{index}", "staging_sha256")) != remote_sha: raise ContractError(ErrorCode.HASH_MISMATCH, "staging", "remote/local sha256") self.timings.record(f"item_{index}_copy", stage, self._monotonic_ns(), "PASS") stage = self._monotonic_ns() pdfinfo = None if synthetic else Path(self.spec.pdfinfo_executable) evidence = validate_pdf( staging_path, pdfinfo_executable=pdfinfo, timeout_ms=self.budget.require(f"item_{index}", "pdfinfo_staging", 20_000), checkpoint=lambda: self.budget.checkpoint(f"item_{index}", "validate_staging"), ) source_state = PackageState.P05_STAGING_VALID if evidence.page_count is not None and detailed.page_count != evidence.page_count: raise ContractError(ErrorCode.PAGE_COUNT_MISMATCH, "page_count", "detail/pdfinfo mismatch") name = "-".join(( _safe_part(detailed.report_date or "UNKNOWN", 10), _safe_part(detailed.institution or "UNKNOWN", 40), _safe_part(detailed.title, 80), )) + ".pdf" destination = Path(self.spec.destination) if not destination.is_absolute(): destination = Path.cwd() / destination published = publish_no_replace( staging_path, destination / name, expected_sha256=remote_sha, pdfinfo_executable=pdfinfo, timeout_ms=self.budget.require(f"item_{index}", "publish_pdfinfo", 20_000), checkpoint=lambda: self.budget.checkpoint(f"item_{index}", "publish"), ) source_state = PackageState.P06_PUBLISHED self.budget.checkpoint(f"item_{index}", "quota_artifact") artifact_event = dict(quota.artifact( task_id=self.spec.task_id, requester_role=self.spec.requester, handoff_id=self.spec.handoff_id, run_id=run_id, slot_id=slot_id, report_identity=report_identity, quota_terminal_event_id=quota_terminal["event_id"], success=not published.duplicate, note="PDF_VALID_PUBLISHED" if not published.duplicate else "IDENTICAL_EXISTING_PDF", )) item_status = ItemStatus.DUPLICATE if published.duplicate else ItemStatus.SUCCESS item_stop = ErrorCode.DUPLICATE_EXISTING_ARTIFACT if published.duplicate else None row_id = hashlib.sha256( f"{run_id}|{item_id}|{report_identity}|{published.evidence.sha256}".encode("utf-8") ).hexdigest() external_hash = hashlib.sha256( f"{match.remote.path}|{match.remote.bytes}|{remote_sha}|{published.evidence.bytes}|{published.evidence.sha256}".encode("utf-8") ).hexdigest() item_terminal_recorded = True if not self._complete_item(index): raise ContractError(ErrorCode.DEADLINE_EXPIRED, f"item_{index}", "item_close") manifest_rows.append(_manifest_row( task_id=self.spec.task_id, requested_by=self.spec.requester, review_owner=self.spec.review_owner, source_url="NOT_APPLICABLE_APP_CACHE", source_site=SOURCE_SITE, title=detailed.title, publisher=detailed.institution, report_date=detailed.report_date, downloaded_at=utc_now(), http_status="NOT_APPLICABLE_APP_CACHE", content_type="application/pdf", file_name=published.final_path.name, relative_path=str(published.final_path), bytes=published.evidence.bytes, sha256=published.evidence.sha256, download_status=item_status.value, error_or_note="IDENTICAL_EXISTING_PDF" if published.duplicate else "NONE", source_cache_path=match.remote.path, source_file_name=match.remote.name, android_package=self.spec.package_name, extension_added="false", pdf_magic_valid="true", remote_sha256=remote_sha, local_sha256=published.evidence.sha256, openability="true", page_count=detailed.page_count, encryption_status="UNKNOWN" if published.evidence.encrypted is None else str(published.evidence.encrypted).lower(), schema_version="HIBOR_REPORT_MANIFEST_V004", row_id=row_id, handoff_id=self.spec.handoff_id, run_id=run_id, item_id=item_id, slot_id=slot_id, query=self.spec.query, candidate_id=detailed.candidate_id, report_identity=report_identity, analysts="|".join(detailed.analysts), selection_reason=f"FAST_SCORE={detailed.score}", remote_bytes=match.remote.bytes, local_bytes=published.evidence.bytes, quota_reservation_id=reservation.reservation_id, quota_terminal_event_id=quota_terminal["event_id"], quota_artifact_event_id=artifact_event["event_id"], status=item_status.value, stop_code=item_stop.value if item_stop else "", reused_without_new_trigger="false", external_evidence_hash=external_hash, manifested_at_utc=utc_now(), )) items.append(terminal_item({ "item_id": item_id, "slot_id": slot_id, "candidate_id": detailed.candidate_id, "report_identity": report_identity, "state": PackageState.P06_PUBLISHED.value, "status": item_status.value, "stop_code": item_stop.value if item_stop else None, "trigger_attempted": True, "triggered": True, "quota_state": QuotaState.CONFIRMED.value, "quota_reservation_id": reservation.reservation_id, "quota_terminal_event_id": quota_terminal["event_id"], "quota_artifact_event_id": artifact_event["event_id"], "title": detailed.title, "institution": detailed.institution, "report_date": detailed.report_date, "analysts": list(detailed.analysts), "page_count": detailed.page_count, "bytes": published.evidence.bytes, "sha256": published.evidence.sha256, "source_cache_path": match.remote.path, "source_file_name": match.remote.name, "final_path": str(published.final_path), "manifest_row_id": row_id, "reused_without_new_trigger": False, "error_or_note": "IDENTICAL_EXISTING_PDF" if published.duplicate else "NONE", })) package_state = PackageState.P06_PUBLISHED self.timings.record(f"item_{index}_validation_publish", stage, self._monotonic_ns(), item_status.value) except ContractError as exc: artifact_event_id = artifact_event["event_id"] if artifact_event else None try: if reservation and reservation.allowed and quota_terminal is None: self.budget.checkpoint(f"item_{index}", "quota_failure_terminal", close=True) event_type = "CONSUME_UNCERTAIN" if action_attempted else "RELEASE" quota_terminal = dict(quota.terminal( reservation, event_type=event_type, task_id=self.spec.task_id, requester_role=self.spec.requester, handoff_id=self.spec.handoff_id, run_id=run_id, slot_id=slot_id, report_identity=report_identity or f"UNKNOWN-{item_id}", note=exc.code.value, )) if (quota_terminal and quota_terminal["event_type"] != "RELEASE" and artifact_event is None): self.budget.checkpoint(f"item_{index}", "quota_failure_artifact", close=True) artifact_event = dict(quota.artifact( task_id=self.spec.task_id, requester_role=self.spec.requester, handoff_id=self.spec.handoff_id, run_id=run_id, slot_id=slot_id, report_identity=report_identity or f"UNKNOWN-{item_id}", quota_terminal_event_id=quota_terminal["event_id"], success=bool(published is not None and not published.duplicate), note=("PDF_VALID_PUBLISHED_DURING_FAILURE_CLOSE" if published is not None and not published.duplicate else exc.code.value), )) artifact_event_id = artifact_event["event_id"] except ContractError as closure_exc: exc = closure_exc if not item_terminal_recorded: item_terminal_recorded = True if not self._complete_item(index): exc = ContractError(ErrorCode.DEADLINE_EXPIRED, f"item_{index}", "item_close") stop_code = exc.code blocker = f"{exc.field_name or '-'}:{exc.detail}" quota_state = self._quota_state_from_terminal(quota_terminal) failure_row = self._failure_manifest_row( run_id=run_id, item_id=item_id, slot_id=slot_id, candidate=detailed, report_identity=report_identity, code=exc.code, note=blocker, reservation=reservation, quota_terminal=quota_terminal, artifact_event_id=artifact_event_id, action_attempted=action_attempted, match=match, remote_sha=remote_sha, published=published, ) manifest_rows.append(failure_row) items.append(terminal_item({ "item_id": item_id, "slot_id": slot_id, "candidate_id": detailed.candidate_id if detailed else None, "report_identity": report_identity, "state": source_state.value, "status": ItemStatus.STOPPED.value, "stop_code": exc.code.value, "trigger_attempted": action_attempted, "triggered": trigger_confirmed, "quota_state": quota_state.value, "quota_reservation_id": reservation.reservation_id if reservation else None, "quota_terminal_event_id": quota_terminal["event_id"] if quota_terminal else None, "quota_artifact_event_id": artifact_event_id, "title": detailed.title if detailed else None, "institution": detailed.institution if detailed else None, "report_date": detailed.report_date if detailed else None, "analysts": list(detailed.analysts) if detailed and detailed.analysts else None, "page_count": (published.evidence.page_count if published and published.evidence.page_count is not None else detailed.page_count if detailed else None), "bytes": published.evidence.bytes if published else None, "sha256": published.evidence.sha256 if published else None, "source_cache_path": match.remote.path if match and match.remote else None, "source_file_name": match.remote.name if match and match.remote else None, "final_path": str(published.final_path) if published else None, "manifest_row_id": failure_row["row_id"], "reused_without_new_trigger": False, "error_or_note": blocker, })) terminal_status = ( TerminalStatus.STATE_UNCERTAIN if quota_state is QuotaState.UNCERTAIN else self._status_for_error(exc.code, any( row["status"] in {ItemStatus.SUCCESS.value, ItemStatus.DUPLICATE.value} for row in items )) ) break if terminal_status is TerminalStatus.SUCCESS and len(items) < self.spec.quantity: stop_code = ErrorCode.CANDIDATE_REJECTED blocker = "INSUFFICIENT_QUALIFIED_CANDIDATES" terminal_status = TerminalStatus.PARTIAL_SUCCESS if items else TerminalStatus.BLOCKED_INPUT try: adb.screenshot(screenshot_root / "99-end.png") except ContractError: if terminal_status is TerminalStatus.SUCCESS: terminal_status = TerminalStatus.PARTIAL_SUCCESS stop_code = ErrorCode.UI_CURSOR_RESTORE_FAILED blocker = "END_SCREENSHOT_FAILED" self.budget.checkpoint("close", "quota_snapshot", batch_close=True) quota_snapshot = quota.snapshot() stage = self._monotonic_ns() if manifest_rows: write_manifest_create_new( manifest_path, manifest_rows, checkpoint=lambda: self.budget.checkpoint("close", "manifest", batch_close=True), ) manifest_verified = True package_state = PackageState.P07_MANIFESTED else: manifest_verified = False self.timings.record("manifest", stage, self._monotonic_ns(), "PASS" if manifest_verified else "ABSENT") stage = self._monotonic_ns() delivery_text = self._delivery_text(run_id, items, quota_snapshot, terminal_status, stop_code) write_bytes_create_new( delivery_path, delivery_text.encode("utf-8"), checkpoint=lambda: self.budget.checkpoint("close", "delivery", batch_close=True), ) delivery_verified = delivery_path.read_bytes() == delivery_text.encode("utf-8") if not delivery_verified: raise ContractError(ErrorCode.PERSIST_LATE, "delivery", "readback mismatch") package_state = PackageState.P08_DELIVERY self.timings.record("delivery", stage, self._monotonic_ns(), "PASS") stage = self._monotonic_ns() timing_value = self._timing_value(run_id) write_json_create_new( timing_path, timing_value, checkpoint=lambda: self.budget.checkpoint("close", "timing", batch_close=True), ) timing_verified = True package_state = PackageState.P09_TIMING self.timings.record("timing", stage, self._monotonic_ns(), "PASS") except ContractError as exc: stop_code = exc.code blocker = f"{exc.field_name or '-'}:{exc.detail}" terminal_status = self._status_for_error(exc.code, bool(items)) except Exception as exc: # fail closed at the public boundary stop_code = ErrorCode.UNEXPECTED_EXCEPTION blocker = f"{type(exc).__name__}:{str(exc)[:240]}" terminal_status = TerminalStatus.INTERNAL_ERROR if quota is not None: try: quota_snapshot = quota.snapshot() except ContractError: terminal_status = TerminalStatus.STATE_UNCERTAIN stop_code = ErrorCode.QUOTA_LEDGER_INVALID blocker = "QUOTA_FINAL_SNAPSHOT_UNCERTAIN" base = self._base_terminal( quota_snapshot, run_id=run_id, items=items, triggered=triggered, manifest_path=manifest_path if manifest_verified else None, delivery_path=delivery_path if delivery_verified else None, timing_path=timing_path if timing_verified else None, ) intended_items = [dict(row, state=PackageState.P10_CLOSED.value) for row in items] intended_base = dict(base) intended_base["items"] = intended_items intended = TerminalReceipt( terminal_path, terminal_path, True, True, EvidenceState.V, True, False, True, 2, "0" * 64, None, PackageState.P10_CLOSED, ) record = build_terminal(intended_base, intended, status=terminal_status, stop_code=stop_code, blocker=blocker) receipt = TerminalWriter().persist( terminal_path, record, source_state=package_state, checkpoint=lambda: self.budget.checkpoint("close", "terminal", batch_close=True), ) if receipt.presence is EvidenceState.V: return build_terminal(intended_base, receipt, status=terminal_status, stop_code=stop_code, blocker=blocker) if (receipt.presence is EvidenceState.N and not receipt.persist_attempted and self.budget.batch_close_deadline_reached): return build_terminal(base, receipt, status=TerminalStatus.TIME_BUDGET_STOP, stop_code=ErrorCode.DEADLINE_EXPIRED, blocker="BATCH_CLOSE_DEADLINE_EXHAUSTED") terminal_status = (TerminalStatus.STATE_UNCERTAIN if receipt.presence is EvidenceState.U else TerminalStatus.INTERNAL_ERROR) return build_terminal(base, receipt, status=terminal_status, stop_code=receipt.error_code or ErrorCode.PERSIST_LATE, blocker="TERMINAL_PERSIST_NOT_VALID") def _quota_snapshot_readonly(self) -> QuotaSnapshot: if self.spec.quota_path.is_file(): local_date = datetime.now(ZoneInfo("Asia/Shanghai")).date().isoformat() return QuotaLedger( self.spec.quota_path, quota_date=local_date, timeout_provider=lambda operation, maximum: self.budget.require( "quota", operation, maximum, close=self.budget.work_deadline_reached), ).snapshot() local_date = datetime.now(ZoneInfo("Asia/Shanghai")).date().isoformat() known = 3 if local_date == "2026-07-29" else 0 return QuotaSnapshot(local_date, 0, 0, 0, known, 0, known, 25 - known, 0) def _begin_item(self, index: int) -> None: self.budget.begin_item(index) if index in self._open_item_windows: return close_deadline = self.budget.active_item_deadline work_deadline = self.budget.active_item_work_deadline if close_deadline is None or work_deadline is None: raise ContractError(ErrorCode.CLOCK_INVALID, f"item_{index}", "deadline unavailable") self._open_item_windows[index] = { "item_index": index, "started_monotonic_ns": self._monotonic_ns(), "work_deadline_monotonic_ns": int(work_deadline * 1_000_000_000), "close_deadline_monotonic_ns": int(close_deadline * 1_000_000_000), } def _complete_item(self, index: int) -> bool: window = self._open_item_windows.pop(index, None) if window is None: raise ContractError(ErrorCode.CLOCK_INVALID, f"item_{index}", "start evidence absent") terminal_ns = self._monotonic_ns() within_deadline = self.budget.complete_item(index) window.update({ "terminal_monotonic_ns": terminal_ns, "elapsed_ms": max(0, (terminal_ns - window["started_monotonic_ns"]) // 1_000_000), "terminal_delta_from_previous_ms": ( None if not self.item_windows else max(0, (terminal_ns - self.item_windows[-1]["terminal_monotonic_ns"]) // 1_000_000) ), "deadline_met": within_deadline, }) self.item_windows.append(window) return within_deadline def _timing_value(self, run_id: str) -> dict[str, Any]: return { "schema_version": "HIBOR_FAST_TIMING_V002", "run_id": run_id, "observed_at_utc": self.spec.observed_at_utc, "budget_ms": self.spec.total_budget_ms, "close_reserve_ms": self.spec.close_reserve_ms, "batch_increment_budget_ms": self.spec.batch_increment_budget_ms, "item_windows": self.item_windows, "rows": self.timings.rows, } def _preflight_terminal(self, run_id: str, terminal_path: Path, quota: QuotaSnapshot, exc: ContractError) -> dict[str, Any]: status = self._status_for_error(exc.code, False) base = self._base_terminal(quota, run_id=run_id, items=[], triggered=0) intended = TerminalReceipt( terminal_path, terminal_path, True, True, EvidenceState.V, True, False, True, 2, "0" * 64, None, PackageState.P10_CLOSED, ) record = build_terminal(base, intended, status=status, stop_code=exc.code, blocker=f"{exc.field_name or '-'}:{exc.detail}") receipt = TerminalWriter().persist( terminal_path, record, source_state=PackageState.P00_INIT, checkpoint=lambda: self.budget.checkpoint("close", "preflight_terminal", batch_close=True), ) if receipt.presence is EvidenceState.V: return build_terminal(base, receipt, status=status, stop_code=exc.code, blocker=f"{exc.field_name or '-'}:{exc.detail}") if (exc.code is ErrorCode.DEADLINE_EXPIRED and receipt.presence is EvidenceState.N and not receipt.persist_attempted): return build_terminal(base, receipt, status=TerminalStatus.TIME_BUDGET_STOP, stop_code=ErrorCode.DEADLINE_EXPIRED, blocker=f"{exc.field_name or '-'}:{exc.detail}") return build_terminal( base, receipt, status=TerminalStatus.STATE_UNCERTAIN if receipt.presence is EvidenceState.U else TerminalStatus.INTERNAL_ERROR, stop_code=receipt.error_code or ErrorCode.PERSIST_LATE, blocker="PREFLIGHT_TERMINAL_PERSIST_NOT_VALID", ) def _discover_project_reuse(self, *, synthetic: bool) -> list[dict[str, Any]]: """Verify immutable project PDF+manifest relationships without opening the APP.""" configured = self.spec.source_scope.get("reuse_manifest_paths", []) if configured is None: configured = [] if not isinstance(configured, list) or any(not isinstance(p, str) for p in configured): raise ContractError(ErrorCode.TASK_SPEC_INVALID, "reuse_manifest_paths", "string array") paths = {Path(p) for p in configured} if self.spec.output_path.exists(): paths.update(self.spec.output_path.rglob("manifest.csv")) results: list[dict[str, Any]] = [] seen: set[str] = set() pdfinfo = None if synthetic else Path(self.spec.pdfinfo_executable) for manifest in sorted(paths, key=lambda p: str(p)): self.budget.checkpoint("reuse", "manifest_read") if not manifest.is_file() or manifest.is_symlink(): continue try: with manifest.open("r", encoding="utf-8", newline="") as stream: reader = csv.DictReader(stream) if tuple(reader.fieldnames or ()) != MANIFEST_COLUMNS: continue source_rows = list(reader) except (OSError, UnicodeError, csv.Error): continue for source in source_rows: if source.get("status") not in {"SUCCESS", "DUPLICATE"}: continue identity = source.get("report_identity", "") if not re.fullmatch(r"[0-9a-f]{64}", identity) or identity in seen: continue try: candidate = Candidate( source.get("candidate_id") or identity[:24], source["title"], source.get("publisher") or None, source.get("report_date") or None, tuple(x for x in source.get("analysts", "").split("|") if x), int(source["page_count"]) if source.get("page_count") else None, ) if _identity(candidate) != identity: continue self._enforce_hard_filters(candidate) final_path = Path(source["relative_path"]) if not final_path.is_absolute(): final_path = Path.cwd() / final_path evidence = validate_pdf( final_path, pdfinfo_executable=pdfinfo, timeout_ms=self.budget.require("reuse", "pdfinfo", 20_000), checkpoint=lambda: self.budget.checkpoint("reuse", "pdf_verify"), ) if (str(evidence.bytes) != source.get("bytes") or evidence.sha256 != source.get("sha256") or (evidence.page_count is not None and str(evidence.page_count) != source.get("page_count"))): continue except ContractError as exc: if exc.code is ErrorCode.DEADLINE_EXPIRED: raise continue except (OSError, ValueError, KeyError): continue seen.add(identity) results.append({"source": source, "candidate": candidate, "evidence": evidence, "final_path": final_path}) ranked = sorted(results, key=lambda row: ( -score_candidate(row["candidate"], query=self.spec.query, aliases=tuple(self.spec.aliases), institutions=tuple(self.spec.institutions), minimum_pages=self.spec.minimum_pages), row["candidate"].candidate_id, )) expected = [row["report_identity"] for row in self.spec.expected_reports] if expected: by_identity = {row["source"]["report_identity"]: row for row in ranked} return [by_identity[identity] for identity in expected if identity in by_identity] return ranked def _close_project_reuse(self, *, run_id: str, run_root: Path, manifest_path: Path, delivery_path: Path, timing_path: Path, terminal_path: Path, rows: list[dict[str, Any]]) -> dict[str, Any]: quota_before = self.spec.quota_path.read_bytes() if self.spec.quota_path.is_file() else None quota = self._quota_snapshot_readonly() manifest_rows, items = self._build_reuse_payload(run_id, rows) write_manifest_create_new( manifest_path, manifest_rows, checkpoint=lambda: self.budget.checkpoint("close", "reuse_manifest", batch_close=True), ) delivery = self._delivery_text(run_id, items, quota, TerminalStatus.SUCCESS, None) write_bytes_create_new( delivery_path, delivery.encode("utf-8"), checkpoint=lambda: self.budget.checkpoint("close", "reuse_delivery", batch_close=True), ) timing_value = self._timing_value(run_id) write_json_create_new( timing_path, timing_value, checkpoint=lambda: self.budget.checkpoint("close", "reuse_timing", batch_close=True), ) quota_after = self.spec.quota_path.read_bytes() if self.spec.quota_path.is_file() else None if quota_before != quota_after: raise ContractError(ErrorCode.QUOTA_LEDGER_INVALID, "pure_reuse", "ledger mutated") closed = [dict(item, state=PackageState.P10_CLOSED.value) for item in items] base = self._base_terminal(quota, run_id=run_id, items=closed, triggered=0, manifest_path=manifest_path, delivery_path=delivery_path, timing_path=timing_path) intended = TerminalReceipt(terminal_path, terminal_path, True, True, EvidenceState.V, True, False, True, 2, "0" * 64, None, PackageState.P10_CLOSED) record = build_terminal(base, intended, status=TerminalStatus.SUCCESS, stop_code=None, blocker=None) receipt = TerminalWriter().persist( terminal_path, record, source_state=PackageState.P09_TIMING, checkpoint=lambda: self.budget.checkpoint("close", "reuse_terminal", batch_close=True), ) if receipt.presence is not EvidenceState.V: return build_terminal(dict(base, items=items), receipt, status=TerminalStatus.STATE_UNCERTAIN if receipt.presence is EvidenceState.U else TerminalStatus.INTERNAL_ERROR, stop_code=receipt.error_code or ErrorCode.PERSIST_LATE, blocker="REUSE_TERMINAL_PERSIST_NOT_VALID") return build_terminal(base, receipt, status=TerminalStatus.SUCCESS, stop_code=None, blocker=None) def _build_reuse_payload(self, run_id: str, rows: list[dict[str, Any]], *, start_index: int = 1) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: manifest_rows: list[dict[str, Any]] = [] items: list[dict[str, Any]] = [] for index, entry in enumerate(rows, start_index): source = entry["source"] candidate: Candidate = entry["candidate"] evidence = entry["evidence"] final_path: Path = entry["final_path"] item_id, slot_id = f"ITEM-{index:03d}", f"SLOT-{index:03d}" row_id = hashlib.sha256( f"{run_id}|{item_id}|{source['report_identity']}|{evidence.sha256}|PURE".encode("utf-8") ).hexdigest() external_hash = hashlib.sha256( f"{source.get('row_id','')}|{evidence.bytes}|{evidence.sha256}|PURE".encode("utf-8") ).hexdigest() row = _manifest_row(**{key: source.get(key) for key in MANIFEST_COLUMNS}) row.update({ "task_id": self.spec.task_id, "requested_by": self.spec.requester, "review_owner": self.spec.review_owner, "downloaded_at": self.started_at, "relative_path": str(final_path), "download_status": ItemStatus.SUCCESS.value, "error_or_note": "ZERO_QUOTA_REUSE", "schema_version": "HIBOR_REPORT_MANIFEST_V004", "row_id": row_id, "handoff_id": self.spec.handoff_id, "run_id": run_id, "item_id": item_id, "slot_id": slot_id, "query": self.spec.query, "quota_reservation_id": None, "quota_terminal_event_id": None, "quota_artifact_event_id": None, "status": ItemStatus.SUCCESS.value, "stop_code": None, "reused_without_new_trigger": "true", "external_evidence_hash": external_hash, "manifested_at_utc": self.started_at, }) manifest_rows.append(_manifest_row(**row)) items.append(terminal_item({ "item_id": item_id, "slot_id": slot_id, "candidate_id": candidate.candidate_id, "report_identity": source["report_identity"], "state": PackageState.P06_PUBLISHED.value, "status": ItemStatus.SUCCESS.value, "stop_code": None, "trigger_attempted": False, "triggered": False, "quota_state": QuotaState.NONE.value, "quota_reservation_id": None, "quota_terminal_event_id": None, "quota_artifact_event_id": None, "title": candidate.title, "institution": candidate.institution, "report_date": candidate.report_date, "analysts": list(candidate.analysts), "page_count": evidence.page_count or candidate.page_count, "bytes": evidence.bytes, "sha256": evidence.sha256, "source_cache_path": source.get("source_cache_path") or None, "source_file_name": source.get("source_file_name") or None, "final_path": str(final_path), "manifest_row_id": row_id, "reused_without_new_trigger": True, "error_or_note": "ZERO_QUOTA_REUSE", })) return manifest_rows, items def _materialize_cache_reuse(self, *, adb: Any, run_root: Path, synthetic: bool) -> list[dict[str, Any]]: raw_items = self.spec.source_scope.get("resume_items", []) if not isinstance(raw_items, list): raise ContractError(ErrorCode.TASK_SPEC_INVALID, "resume_items", "array required") self._begin_item(1) self.budget.checkpoint("cache_reuse", "adb_preflight") adb.preflight() files = {item.path: item for item in adb.list_cache()} output: list[dict[str, Any]] = [] pdfinfo = None if synthetic else Path(self.spec.pdfinfo_executable) for index, raw in enumerate(raw_items, 1): if not isinstance(raw, Mapping): raise ContractError(ErrorCode.TASK_SPEC_INVALID, "resume_items", "mapping required") required = {"candidate_id", "report_identity", "title", "institution", "report_date", "analysts", "page_count", "remote_path", "remote_bytes", "remote_sha256"} if set(raw) != required: raise ContractError(ErrorCode.TASK_SPEC_INVALID, "resume_items", "exact keys required") self._begin_item(index) candidate = Candidate( str(raw["candidate_id"]), str(raw["title"]), str(raw["institution"]), str(raw["report_date"]), tuple(str(x) for x in raw["analysts"]), int(raw["page_count"]), ) if _identity(candidate) != raw["report_identity"]: raise ContractError(ErrorCode.DETAIL_RESULT_MISMATCH, "report_identity", "cache identity") self._enforce_hard_filters(candidate) remote = files.get(str(raw["remote_path"])) if remote is None or remote.bytes != int(raw["remote_bytes"]): raise ContractError(ErrorCode.CACHE_AMBIGUOUS, "remote_path", "recorded cache object absent/drift") remote_sha = adb.remote_sha256(remote.path) if remote_sha != raw["remote_sha256"]: raise ContractError(ErrorCode.HASH_MISMATCH, "remote_sha256", "recorded cache hash drift") staging = run_root / "staging" / f"ITEM-{index:03d}.pdf" adb.pull(remote.path, staging, timeout_ms=self.budget.require(f"item_{index}", "cache_reuse_pull", 60_000)) name = "-".join((_safe_part(candidate.report_date or "UNKNOWN", 10), _safe_part(candidate.institution or "UNKNOWN", 40), _safe_part(candidate.title, 80))) + ".pdf" destination = Path(self.spec.destination) if not destination.is_absolute(): destination = Path.cwd() / destination published = publish_no_replace( staging, destination / name, expected_sha256=remote_sha, pdfinfo_executable=pdfinfo, timeout_ms=self.budget.require(f"item_{index}", "cache_reuse_publish", 20_000), checkpoint=lambda: self.budget.checkpoint(f"item_{index}", "cache_reuse_publish"), ) if published.evidence.page_count is not None and published.evidence.page_count != candidate.page_count: raise ContractError(ErrorCode.PAGE_COUNT_MISMATCH, "page_count", "cache reuse") source = {key: "" for key in MANIFEST_COLUMNS} source.update({ "source_url": "NOT_APPLICABLE_APP_CACHE", "source_site": SOURCE_SITE, "title": candidate.title, "publisher": candidate.institution, "report_date": candidate.report_date, "http_status": "NOT_APPLICABLE_APP_CACHE", "content_type": "application/pdf", "file_name": published.final_path.name, "relative_path": str(published.final_path), "bytes": str(published.evidence.bytes), "sha256": published.evidence.sha256, "source_cache_path": remote.path, "source_file_name": remote.name, "android_package": self.spec.package_name, "extension_added": "false", "pdf_magic_valid": "true", "remote_sha256": remote_sha, "local_sha256": published.evidence.sha256, "openability": "true", "page_count": str(candidate.page_count), "encryption_status": "UNKNOWN" if published.evidence.encrypted is None else str(published.evidence.encrypted).lower(), "candidate_id": candidate.candidate_id, "report_identity": raw["report_identity"], "analysts": "|".join(candidate.analysts), "selection_reason": "CACHE_REUSE_VERIFIED", "remote_bytes": str(remote.bytes), "local_bytes": str(published.evidence.bytes), "row_id": hashlib.sha256(f"CACHE|{raw['report_identity']}|{remote_sha}".encode()).hexdigest(), }) output.append({"source": source, "candidate": candidate, "evidence": published.evidence, "final_path": published.final_path}) if not self._complete_item(index): raise ContractError(ErrorCode.DEADLINE_EXPIRED, f"item_{index}", "item_close") return output def _close_reuse_item_windows(self, count: int) -> None: for index in range(1, count + 1): self._begin_item(index) if not self._complete_item(index): raise ContractError(ErrorCode.DEADLINE_EXPIRED, f"item_{index}", "item_close") def _run_id(self) -> str: canonical = json.dumps(self.spec.raw, ensure_ascii=False, separators=(",", ":"), sort_keys=False).encode("utf-8") token = hashlib.sha256(b"HIBOR-RUN-V001\x00" + canonical).hexdigest()[:24] task = re.sub(r"[^A-Za-z0-9_.-]", "-", self.spec.task_id)[:80] return f"RUN-{task}-{token}" @staticmethod def _status_for_error(code: ErrorCode, has_success: bool) -> TerminalStatus: if code in {ErrorCode.PROCESS_LIVENESS_UNKNOWN, ErrorCode.RECOVERY_UNKNOWN, ErrorCode.TRIGGER_UNCERTAIN}: return TerminalStatus.STATE_UNCERTAIN if code is ErrorCode.ACCESS_CONTROL_PRESENT: return TerminalStatus.BLOCKED_ACCESS_CONTROL if code is ErrorCode.DEADLINE_EXPIRED: return TerminalStatus.TIME_BUDGET_STOP if code is ErrorCode.QUOTA_EXHAUSTED: return TerminalStatus.PARTIAL_QUOTA_STOP if has_success: return TerminalStatus.PARTIAL_SUCCESS if code in {ErrorCode.PDF_MAGIC_INVALID, ErrorCode.BYTE_COUNT_MISMATCH, ErrorCode.HASH_MISMATCH, ErrorCode.PDF_NOT_OPENABLE, ErrorCode.PAGE_COUNT_MISMATCH, ErrorCode.PDF_ENCRYPTED}: return TerminalStatus.VALIDATION_FAILED if code in {ErrorCode.CANDIDATE_REJECTED, ErrorCode.DETAIL_RESULT_MISMATCH}: return TerminalStatus.BLOCKED_INPUT return TerminalStatus.BLOCKED_ENVIRONMENT @staticmethod def _quota_state_from_terminal(quota_terminal: dict[str, str] | None) -> QuotaState: """Project the durable quota terminal without rewriting confirmed facts.""" if quota_terminal is None: return QuotaState.NONE event_type = quota_terminal.get("event_type") if event_type == "CONSUME_CONFIRMED": return QuotaState.CONFIRMED if event_type == "CONSUME_UNCERTAIN": return QuotaState.UNCERTAIN if event_type == "RELEASE": return QuotaState.RELEASED raise ContractError(ErrorCode.QUOTA_LEDGER_INVALID, "quota_terminal", f"unexpected event_type {event_type!r}") def _failure_manifest_row(self, *, run_id: str, item_id: str, slot_id: str, candidate: Candidate | None, report_identity: str | None, code: ErrorCode, note: str, reservation: Reservation | None, quota_terminal: dict[str, str] | None, artifact_event_id: str | None, action_attempted: bool, match: Any | None = None, remote_sha: str | None = None, published: Any | None = None) -> dict[str, Any]: identity = report_identity or f"UNKNOWN-{item_id}" row_id = hashlib.sha256( f"{run_id}|{item_id}|{identity}|{code.value}".encode("utf-8") ).hexdigest() remote = match.remote if match is not None else None if published is not None: external_hash = hashlib.sha256( (f"{remote.path if remote else ''}|{remote.bytes if remote else ''}|" f"{remote_sha or ''}|{published.evidence.bytes}|" f"{published.evidence.sha256}").encode("utf-8") ).hexdigest() download_status = (ItemStatus.DUPLICATE.value if published.duplicate else ItemStatus.SUCCESS.value) else: external_hash = hashlib.sha256( f"{identity}|{code.value}|{note}".encode("utf-8") ).hexdigest() download_status = ItemStatus.STOPPED.value return _manifest_row( task_id=self.spec.task_id, requested_by=self.spec.requester, review_owner=self.spec.review_owner, source_url="NOT_APPLICABLE_APP_CACHE" if published else "UNKNOWN", source_site=SOURCE_SITE, title=candidate.title if candidate else "UNKNOWN", publisher=candidate.institution if candidate and candidate.institution else "UNKNOWN", report_date=candidate.report_date if candidate and candidate.report_date else "UNKNOWN", downloaded_at=utc_now(), http_status="NOT_APPLICABLE_APP_CACHE" if published else "UNKNOWN", content_type="application/pdf" if published else "UNKNOWN", file_name=published.final_path.name if published else None, relative_path=str(published.final_path) if published else None, bytes=published.evidence.bytes if published else None, sha256=published.evidence.sha256 if published else None, download_status=download_status, error_or_note=note, source_cache_path=remote.path if remote else None, source_file_name=remote.name if remote else None, android_package=self.spec.package_name, extension_added="false", pdf_magic_valid="true" if published else "UNKNOWN", remote_sha256=remote_sha, local_sha256=published.evidence.sha256 if published else None, openability="true" if published else "UNKNOWN", page_count=(published.evidence.page_count if published and published.evidence.page_count is not None else candidate.page_count if candidate else None), encryption_status=("UNKNOWN" if not published or published.evidence.encrypted is None else str(published.evidence.encrypted).lower()), schema_version="HIBOR_REPORT_MANIFEST_V004", row_id=row_id, handoff_id=self.spec.handoff_id, run_id=run_id, item_id=item_id, slot_id=slot_id, query=self.spec.query, candidate_id=candidate.candidate_id if candidate else None, report_identity=identity, analysts="|".join(candidate.analysts) if candidate and candidate.analysts else None, selection_reason="FAST_SELECTED" if candidate else "NOT_SELECTED", remote_bytes=remote.bytes if remote else None, local_bytes=published.evidence.bytes if published else None, quota_reservation_id=reservation.reservation_id if reservation else None, quota_terminal_event_id=quota_terminal["event_id"] if quota_terminal else None, quota_artifact_event_id=artifact_event_id, status=download_status, stop_code=code.value, reused_without_new_trigger="false", external_evidence_hash=external_hash, manifested_at_utc=utc_now(), ) def _enforce_hard_filters(self, candidate: Candidate) -> None: title = candidate.title.casefold() required_terms = [self.spec.query.casefold(), *(str(x).casefold() for x in self.spec.aliases)] if not any(term and term in title for term in required_terms): raise ContractError(ErrorCode.CANDIDATE_REJECTED, "title", "query/alias absent") if any(str(term).casefold() in title for term in self.spec.exclude if str(term)): raise ContractError(ErrorCode.CANDIDATE_REJECTED, "exclude", "excluded title term") if self.spec.institutions and candidate.institution not in set(self.spec.institutions): raise ContractError(ErrorCode.CANDIDATE_REJECTED, "institution", "not allowed") if self.spec.analysts and not set(candidate.analysts).intersection(self.spec.analysts): raise ContractError(ErrorCode.CANDIDATE_REJECTED, "analysts", "no required analyst") if candidate.page_count is None or candidate.page_count < self.spec.minimum_pages: raise ContractError(ErrorCode.CANDIDATE_REJECTED, "page_count", "below minimum") if self.spec.date_range is not None: value = self.spec.date_range if isinstance(value, dict): start, end = value.get("start"), value.get("end") elif isinstance(value, (list, tuple)) and len(value) == 2: start, end = value else: raise ContractError(ErrorCode.TASK_SPEC_INVALID, "date_range", "start/end required") if not isinstance(start, str) or not isinstance(end, str) or start > end: raise ContractError(ErrorCode.TASK_SPEC_INVALID, "date_range", "invalid bounds") if candidate.report_date is None or not (start <= candidate.report_date <= end): raise ContractError(ErrorCode.CANDIDATE_REJECTED, "report_date", "outside date range") def _base_terminal(self, quota: QuotaSnapshot, *, run_id: str, items: list[dict[str, Any]] | None = None, triggered: int = 0, manifest_path: Path | None = None, delivery_path: Path | None = None, timing_path: Path | None = None) -> dict[str, Any]: items = items or [] succeeded = sum(row["status"] == ItemStatus.SUCCESS.value for row in items) duplicates = sum(row["status"] == ItemStatus.DUPLICATE.value for row in items) failed = sum(row["status"] in {ItemStatus.FAILED.value, ItemStatus.STOPPED.value} for row in items) now = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") return { "project_id": self.spec.project_id, "task_id": self.spec.task_id, "handoff_id": self.spec.handoff_id, "source_role_instance_id": self.spec.source_role_instance_id, "source_thread_id": self.spec.source_thread_id, "target_role_instance_id": self.spec.target_role_instance_id, "target_thread_id": self.spec.target_thread_id, "reply_thread_id": self.spec.reply_thread_id, "requester": self.spec.requester, "review_owner": self.spec.review_owner, "run_id": run_id, "mode": self.spec.mode, "observed_at_utc": self.spec.observed_at_utc, "started_at_utc": self.started_at, "ended_at_utc": now, "total_elapsed_ms": self.budget.elapsed_ms, "work_deadline_reached": self.budget.work_deadline_reached, "close_deadline_reached": self.budget.close_deadline_reached, "requested": self.spec.quantity, "triggered": triggered, "succeeded": succeeded, "failed": failed, "duplicates": duplicates, "gaps": max(0, self.spec.quantity - succeeded - duplicates), "quota_confirmed": quota.confirmed, "quota_uncertain": quota.uncertain, "quota_active": quota.active, "quota_cumulative_consumed": quota.effective_consumed, "quota_safe_available": quota.safe_available, "quota_ledger_path": str(self.spec.quota_path), "items": sorted(items, key=lambda row: row["item_id"]), "manifest_path": str(manifest_path) if manifest_path else None, "delivery_path": str(delivery_path) if delivery_path else None, "timing_path": str(timing_path) if timing_path else None, "prohibited_action_attestation": { "remote_original_modified": False, "credential_persisted": False, "access_control_bypassed": False, "body_parsed": False, "research_conclusion_generated": False, "external_message_sent": False, }, } def _delivery_text(self, run_id: str, items: list[dict[str, Any]], quota: QuotaSnapshot, status: TerminalStatus, stop_code: ErrorCode | None) -> str: rows = [ "# 慧博研报采集交付回执", "", f"- run_id: `{run_id}`", f"- task_id: `{self.spec.task_id}`", f"- status: `{status.value}`", f"- stop_code: `{stop_code.value if stop_code else 'NONE'}`", f"- requested/triggered: `{self.spec.quantity}/{sum(x['triggered'] for x in items)}`", f"- quota confirmed/uncertain/active: `{quota.confirmed}/{quota.uncertain}/{quota.active}`", "", "## 原件元数据", "", ] for item in items: rows.append( f"- {item['item_id']}: {item['status']} | {item['title'] or 'UNKNOWN'} | " f"{item['bytes'] if item['bytes'] is not None else 'UNKNOWN'} bytes | " f"{item['sha256'] or 'UNKNOWN'}" ) rows.extend(("", "正文未解析;PDF 与 SHA-256 为权威原件。", "")) return "\n".join(rows)