Metadata-Version: 2.4
Name: keble-video-enrichment
Version: 0.2.0
Summary: Media ingestion, processing and analysis service for keble short videos (PlayRef -> EnrichmentResult).
Requires-Python: >=3.13
Requires-Dist: alibabacloud-oss-v2<2,>=1
Requires-Dist: httpx[socks]>=0.27
Requires-Dist: keble-db<2.0.0,>=1.9.0
Requires-Dist: keble-helpers<2.0.0,>=1.51.0
Requires-Dist: openai<3.0.0,>=2.0.0
Requires-Dist: pillow<13.0.0,>=11.0.0
Requires-Dist: prometheus-client<1,>=0.21
Requires-Dist: pydantic-settings>=2
Requires-Dist: pydantic<3.0.0,>=2.10.0
Provides-Extra: data-infra
Requires-Dist: keble-data-infra-contract==0.2.0; extra == 'data-infra'
Provides-Extra: local-asr
Requires-Dist: faster-whisper==1.2.1; extra == 'local-asr'
Provides-Extra: local-ocr
Requires-Dist: paddleocr==3.5.0; extra == 'local-ocr'
Requires-Dist: paddlepaddle==3.3.0; extra == 'local-ocr'
Provides-Extra: rest
Requires-Dist: fastapi<1,>=0.115; extra == 'rest'
Requires-Dist: uvicorn<1,>=0.34; extra == 'rest'
Description-Content-Type: text/markdown

# keble-video-enrichment

短视频媒体处理与理解服务。可通过 `keble-data-infra-contract` 接收通用视频引用
（`ShortVideo` + `PlayRef`），
完成安全摄取、校验、checksum、ffprobe、音频/关键帧提取、ASR、OCR，输出结构化
`EnrichmentResult` 供 Data Infra / Search / GraphRAG 消费。

> `keble-tiktok` 负责找到视频，`keble-video-enrichment` 负责处理和理解视频。

## 0.2.0 exact metering release

The public package now resolves `keble-helpers>=1.51.0`, `keble-db>=1.9.0`,
and `keble-data-infra-contract==0.2.0` entirely from the registry. No Git source
override or repository credential is required to install or build the service.
Existing `0.1.x` deployments have no production data migration obligation; the
first operational rollout may start from a clean database and object prefix.

Side effects if changes:

- Data Infra consumes terminal attempt history and canonical usage from the
  `0.2.0` contract before the Ideas platform writes immutable cost facts.
- ASR/OCR route selection, quota profiles, price evidence, and credentials stay
  environment-owned by this service; package publication does not activate a
  route or claim a 100K/day production quota.

## 财务用量与批量准入

本服务只拥有媒体执行事实与路由策略，不拥有货币价格。每个 ASR、OCR、本地媒体处理结果均发出
`UsageAccountingEvent`，精确区分音频秒、图像帧、token 与本地 compute millisecond；事件携带不可变的
producer attempt/profile identity，但不携带单价或金额。终态 Job 以 schema v2 保存每次分析尝试，因此
全失败调用与“先失败、后重试成功”的调用都能由 Data Infra 传给 Platform 财务账本，不能因没有 Result
而消失。修改这些事件或 identity 时，必须同时检查 `application/analysis.py`、
`application/service.py`、`domain/job.py` 与 Data Infra consumer contract。

ASR 与 OCR 在完成共同的媒体准备后并发执行，并先分别预留预算；最终账面事实按实际 provider 响应结算，
不会把预留值冒充实际金额。批量准入由可替换的 `QuotaAdmissionStore` 管理：测试可用进程内实现，生产必须
使用 Redis Lua 原子脚本，在所有 replica 之间共享并发 lease 与 UTC 日额度。默认上限为 16 个并发 Job、
每天 7,500 个 Job，lease 必须长于 Job timeout；这些是安全容量设置，不是 provider RPM/TPM 或价格。
生产如缺 Redis、额度小于目标视频量或 lease 配置不安全会在启动时 fail closed。

Hosted ASR/OCR 另有一层精确到 operation/provider/deployment/region/billing-mode 的 immutable quota profile。
`scripts/compile_provider_quota_profiles.py` 校验来源、时间、选择器与 content-addressed key；生产必须通过
`KVE_PROVIDER_QUOTA_PROFILE_PATH` 加载 compiled file，并设置 `KVE_REQUIRE_PROVIDER_QUOTA_COVERAGE=1`。
Redis Lua 会原子预留 RPM、input/output/total TPM、并发、audio-seconds/day 与 provider credits/day；拒绝不会
留下部分计数，crash lease 会按 TTL 回收。默认 5,000 个三分钟视频、30% headroom 的 preflight 至少要求
ASR 5 RPM、remote OCR 55 RPM，因此当前 3-RPM Whisper deployment 会明确拒绝启动，而不是在运行中排队到
第二天。`OPENAI_MAX_RETRIES=0` 是 production 要求：physical retry 必须成为可计量的 Job attempt，不能藏在
SDK 内。示例见 [`docs/examples/provider-quota-profiles.example.uncompiled.json`](docs/examples/provider-quota-profiles.example.uncompiled.json)，
容量与 live 证据见 [`docs/benchmarks/2026-07-13-video-capacity-and-live-calibration.md`](docs/benchmarks/2026-07-13-video-capacity-and-live-calibration.md)。

## 项目是什么 / 不是什么

**是**：由 Keble 根据现有系统边界实现的媒体摄取与分析服务；包含 Job 状态、幂等、重试、部分成功、
Artifact 生命周期和 ASR/OCR 规范化。AWS Media2Cloud 仅作为成熟架构参照，用于验证职责分离方向，
不是实现模板或运行时依赖。

**不是**：视频数据源（不调用 EchoTik/FastMoss 私有 API）；通用 Contract 仓库（不定义 `ShortVideo`）；
Web 管理后台/MAM/内容审核平台/视频编辑工具。

## 为什么独立

媒体处理是长耗时、重依赖（ffmpeg/ASR/OCR）、易部分失败的工作负载，与轻量 contract 层和渠道
连接器的定位冲突。`keble-data-infra` 执行计划的决策 D5 已锁定独立仓库。
详见 [ADR-001](docs/adr/ADR-001-independent-video-enrichment-project.md)。

## 与相邻仓库的关系

- **keble-data-infra-contract**：本项目可选 `data-infra` adapter 的唯一上游 package
  依赖（`ShortVideo`/`PlayRef`/`ProviderId`）；Git source 暂时固定到已推送 contract commit，
  私有源码安装需要 repository credential，package 发布后改为 registry version。独立 REST/OCR
  image 不安装这个可选 adapter，因此 production image 不包含 Git 或源码凭证。
- **keble-data-infra-api**：通过 HTTP 提交/读取 enrichment，并实现 contract 中的 consumer
  Protocol；本项目不导入 API/runtime distribution。
- **keble-tiktok**：互不依赖。它产出 PlayRef，本项目消费标准化后的引用；渠道差异在进入本项目前
  已被抹平。详见 [ADR-004](docs/adr/ADR-004-contract-ownership.md)。

## 成熟参照与 Keble 自主决策

AWS Media2Cloud 用于验证稳定资产身份、摄取与分析分离、完整性校验、持久化任务状态和完成通知等原则。
本项目没有复制其 Step Functions、Lambda、DynamoDB、SNS、MediaConvert、Transcribe、Rekognition 或完整
部署模板。Keble 根据轻量部署、现有 MongoDB/Redis、可替换 AI Engine、合规保留策略和 Data Infra 边界，
独立定义并实现了 Domain Contract、Application Workflow、Repository、Adapter、部分成功和 durable
CompletionEvent 交付。

代码与测试证据见[架构文档](docs/architecture/keble-video-enrichment-architecture.md)；逐组件参照范围和未采用
项见 [Media2Cloud mapping](docs/references/media2cloud-mapping.md)。

## MVP

一个 PlayRef → 安全获取 → 校验 + checksum → ffprobe → 音频/关键帧提取 → 一次 ASR + 一次 OCR
→ 结构化 JSON → 清理临时原始媒体。合规 gate 通过前，开发全程使用本地合成测试视频。

## 目标架构

当前实现通过 Port 连接 FastAPI、异步本地 Runner、MongoDB、Aliyun OSS 和 Redis Stream；这些选择来自
Keble 的部署条件与已批准决策，不表示已具备 AWS 式多机编排或生产弹性。
全部图与规则：[docs/architecture/keble-video-enrichment-architecture.md](docs/architecture/keble-video-enrichment-architecture.md)。

## 当前状态

**Phase 00（规划）与 Reference Baseline Reconciliation 已完成；Phase 01（Project Baseline and
Contracts）、Phase 02（Single Video Ingestion）、Phase 03（Media Processing）与 Phase 04（ASR/OCR）
已完成；Phase 04 的 Azure OCR adapter 与独立 REST service 已使用仓库自有合成媒体通过真实
`gpt-5.6-luna` smoke；Azure hosted Whisper 与 Azure Speech Batch 已在同一组自有 EN/ZH fixture 上完成
真实对比，production route 仍等待 owner threshold/label approval。Phase 05 已完成 MongoDB 编排持久化；
Phase 06 已完成 REST Job/Result 查询、
durable Mongo outbox、keble-db Redis Stream publisher 与 v1 consumer contract。真实 Mongo/Redis/
provider 测试保留显式 gate。**
Phase 07 应用开发已完成：通用 Artifact Store、Aliyun OSS adapter、配置/wiring、结构化日志、Prometheus、
quota、清理调度、Linux x86_64 Docker runtime、runbook 与安全评审均已落地。新的深圳开发 bucket 已通过
delete/list preflight、put/head、完整与 range get、checksum、最终 delete 和空前缀审计。Bucket-level
`oss:ListObjects` 与 object-level CRUD policy 已形成可用绑定，因此冷启动 artifact lookup、TTL cleanup
所需的完整 OSS live gate 已通过；`users/phase-07-live-e2e/` 下残留对象计数为零。
`EnrichmentSettings.from_env()` 是 runtime/test 读取 `KVE_*` 的单一强类型入口；显式构造 adapter
settings 时直接传 Pydantic 字段，不再把 `_env_file` 这个框架内部参数散落到调用方。
所有 settings loader 都把 `.env.example` 中空白的可选成本/凭证值视为“未配置”，不会把空字符串
误解析成整数，也不会把未知成本错误地当作零成本；仍为必填的 benchmark threshold 保持 fail-closed。
示例凭证保持空白而不是可被误认作真实 key 的占位字符串，因此复制 `.env.example` 不会抢先选择
错误的 OpenAI/Azure provider；真实 secret 只写入 gitignored `.env` 或部署 secret store。
共享 dotenv 同时包含 `OPENAI_*_MODEL` 与 Azure deployment 时，model/provenance 也严格按已选 provider
解析，OpenAI 默认值不得覆盖 Azure deployment。若音频与 OCR 位于不同 Azure resources，
`AZURE_KEY_ASR`/`AZURE_ENDPOINT_ASR` 独立拥有 ASR credential boundary，而 `AZURE_KEY`/`AZURE_ENDPOINT`
只归 OCR 使用；ASR 不会静默 fallback 到 OCR resource，禁止把一个 resource 的 key 与另一个 endpoint 拼接。
Azure OCR 使用 Responses API，`AZURE_OCR_VERSION` 必须为 `2025-03-01-preview` 或更新版本；
兼容 OpenAI Audio route 独立使用 `AZURE_ASR_VERSION`/`AZURE_ASR_DEPLOYMENT`。大规模 route 另有
Azure Speech batch、OpenAI mini transcription 与 baked local Whisper Turbo 三种强类型选择；禁止让
audio transcription 与 OCR 共享 deployment/API version。
`tests/live/test_synthetic_video_ocr_service_live.py` 覆盖仓库自有视频下载、ffmpeg keyframe、真实 OCR、
Job/Result 持久化、REST 读取、completion event 与 workspace 清理；它不替代 OSS 或 ASR gate。
`tests/live/test_owned_sample_video_batch_live.py` 以显式付费 gate 将 umbrella `docs/medias/sample-vid*.mp4`
五个不同视频逐一走完 HTTP fetch、ffprobe、duration-aware 单帧选择、真实 OCR、result/event 与 workspace
清理；每个视频硬限制一个唯一帧/一次 provider call，避免把验收测试变成无界模型支出。
`tests/live/test_owned_sample_video_local_asr_live.py` 在禁止网络的 baked Whisper image 中将同一批五个视频
走完 fetch、ffprobe、audio extraction、本地 ASR、result/event 与 workspace 清理；`121.945s` 音频在该
Docker CPU host 上用 `161.838s` 完成，识别出四个 English 与一个 Vietnamese 样本，ASR provider call 为零。
这些结果证明执行链和语言输出，不代表文字准确率已经通过 owner 的人工验收。
两个 owned-media live tests 使用 `pytest -s` 时都会输出无凭证的 `human_review_samples`，使 owner 能逐视频
检查 transcript/OCR text；测试仍只在 `tmp_path` 写中间文件并清理 workspace。
当前非 root production image 已使用现有认证 Mongo/Redis、typed OSS/Azure 配置启动并达到 Docker
`healthy`；验证使用唯一 Mongo/Redis namespace，停止后已清理，且未提交媒体或写入 OSS。
RAM policy 若限制写入既有用户前缀，live gate 通过 `KVE_ALIYUN_OSS_PREFIX` 注入该前缀并在其下追加
唯一 `phase-07-live-e2e/{run_id}`；测试先对不存在的唯一 key 验证 `DeleteObject` 权限，无法清理时在
上传前失败，且不得绕过前缀向 bucket root 写入。
共享 backend env 使用 `oss-cn-shenzhen` region spelling，而 Alibaba v2 signer 使用 `cn-shenzhen`；
`AliyunOssSettings` 在 Pydantic boundary 统一这两种输入，避免 live/runtime 产生无效签名。

## 规模化成本边界

当前 `AsrEngine`/`OcrEngine` Port 与 normalized Result contract 继续作为唯一分析边界。关键帧已改为按
完整 duration 抽取有界 candidates、按时间窗口
选择视觉变化最大的画面，并用 64-bit perceptual hash 去重；静态/重复画面不会再重复触发 OCR，十帧预算也
不再只覆盖视频开头十秒。`OcrEngine` 接收完整 `ExtractedFrame`，因此 OCR 与 confidence fallback 会保留
scene selection 的真实 source timestamp，不再用数组下标伪造秒数。
`Dockerfile.paddle` 现提供 x86_64 PP-OCRv5 Mobile mass-volume profile：一个 local batch 处理全部唯一帧，
`ConfidenceFallbackOcrEngine` 只把空结果、未知 confidence 或低于阈值的帧发送到付费 vision adapter；
base `Dockerfile` 保留为 compatibility/canary profile，不是海量 OCR 部署入口。PP-OCR model 在 image build
阶段缓存，production startup 会验证 `inference.json`/weights/config，禁止 runtime 静默下载。
模型选择与 batch `predict()` 边界分别依据
[PP-OCRv5 official comparison](https://www.paddleocr.ai/main/en/version3.x/algorithm/PP-OCRv5/PP-OCRv5.html)
和 [official OCR pipeline API](https://www.paddleocr.ai/main/en/version3.x/pipeline_usage/OCR.html)。
ASR analysis artifact 使用 lossless 16 kHz mono FLAC。ffmpeg `silencedetect` 先建立 source-to-analysis
timeline，再删除静音并保留 speech padding；ASR segment 会映射回原视频时间。若上游提供由
`keble-data-infra` 授权、绑定 source reference 与实际 media SHA-256 的 canonical transcript，则服务直接复用，
跳过音频提取和付费 ASR；caption 文本本身不会被误当作授权 transcript。

ASR route registry 只允许显式配置的 `openai_mini`、`azure_batch` 或 `local_whisper`，不做静默 provider
fallback。Hosted default 为 JSON-only `gpt-4o-mini-transcribe`；没有 provider segment timestamps 时，整段
文本使用 compact audio 的完整 duration，而不会伪造零长度。Azure Speech batch 固定当前实现支持的 GA
`2025-10-15`；production composition 通过 canonical audio `artifact_id` 从 OSS 生成一小时 HTTPS signed URL，
URL 不写入 Job/Result/log，且其 TTL 必须长于 bounded batch timeout。local route 固定 baked
`large-v3-turbo` CTranslate2 artifacts，并禁止 runtime download。
三条 adapter 共用 normalized transcript、VAD timeline 和 cost estimator，但 owner 已选择 public-API-only
production policy，因此最终 selector 只比较 hosted Whisper 与 Azure Speech batch；local Whisper 仅保留为
offline engineering canary。`AnalysisCostBudget` 在 provider call
前 fail closed：未知价格或 worst-case estimate 超预算会返回 typed `ANALYSIS_BUDGET_EXCEEDED`，不会先花费
再报警。

仓库自有中英文语音 fixture、manifest SHA-256、WER/CER、realtime factor 与 cost/min runner 已落地；fixture
标记为 `machine_generated_pending_owner_review`。Baked local Whisper route 已独立测量；hosted mini 可通过
`RUN_LIVE_OPENAI_ASR=1` 独立测量而不伪造 acceptance threshold。ASR-specific Azure Whisper 已在相同
EN/ZH fixtures 上通过：WER `0.0909`、CER `0.0625`、`6.596s` audio / `6.764s` wall、realtime factor
`0.975`；repeat warm run 为 `4.445s` wall / `1.484` realtime factor。Microsoft public retail defaults are
`6000` micros/audio-minute for global Azure Whisper and `3000` for standard Azure Speech batch, reviewed
2026-07-12; regional Whisper deployments override the default through env. Recommended canary thresholds are
WER `0.15`, CER `0.10`, realtime factor `0.5`, and cost ceiling `8000`, but route selection still fails closed until
`KVE_ASR_BENCHMARK_OWNER_APPROVED=1`. The authenticated two-public-route measurement now passes with automatic
isolated OSS upload/sign/delete: Azure Speech Batch measured WER `0.0909`, CER `0.046875`, `133.34s` wall,
realtime factor `0.0495`, and `3002` micros/audio-minute; hosted Whisper measured WER `0.0909`, CER `0.0625`,
`5.47s` wall, realtime factor `1.206`, and `5995` micros/audio-minute. Both processed `6.596s` of identical audio.
Under the recommended short-video threshold, hosted Whisper is the only passing speed candidate and therefore remains
the default. Azure Batch remains a cheaper backlog candidate only after a separate multi-file coordinator proves
forecast bulk capacity; one-job-per-video queue latency must not be mislabeled as throughput. 仓库自有 OCR fixture
已在非 root amd64 Paddle image 中通过真实 PP-OCRv5 Mobile inference：`KEBLE OCR 42`、原始 `9750 ms`
timestamp、`provider_calls=0`。Direct OpenAI and local-compute prices remain operator-owned and unknown rather than
reusing Azure rates or being mislabeled free.

The owner has now approved those thresholds. A fresh configured-route run selects
`openai_mini` with WER `0.0909`, CER `0.0625`, realtime factor `0.7945`, and
`5995` micros/audio-minute. The repository installs `httpx[socks]` because the
OpenAI/Azure client intentionally honors the environment-owned SOCKS proxy; it
must not bypass that network boundary. Azure Speech Batch remains a separately
credentialed, currently unprovisioned backlog candidate—not an implicit reuse of
`AZURE_ENDPOINT_ASR`/`AZURE_KEY_ASR`.

The provider-quota gate is separate from route-quality approval. Compile an
operator-reviewed profile before production startup:

```bash
uv run python scripts/compile_provider_quota_profiles.py \
  docs/examples/provider-quota-profiles.example.uncompiled.json \
  /run/keble/provider-quota-profiles.json
```

The checked-in example intentionally records the observed 3-RPM ASR quota and
therefore compiles as evidence but fails the 5,000-video capacity preflight. Do
not raise that value without a provider/source record, and do not treat a
missing value as unlimited.

Routing 与 gate 配置见 [`.env.example`](.env.example)。不具备 ffmpeg/ffprobe 的主机使用 portable-offline
命令；依赖这些 production binaries 的 media integration modules 明确标记为 `container`，并在 Linux
image/CI 的完整 `not live` suite 中执行：

```bash
uv run pytest -q -m "not live and not slow and not eval and not local_stack and not db_stack and not container"
uv run pytest -q -m eval
uv run ruff check .
npx --yes pyright .
```

真实 route 分别使用 `RUN_LIVE_OPENAI_ASR`、`RUN_LIVE_AZURE_SPEECH_BATCH`、
`RUN_LOCAL_WHISPER_ASR`；public-API measurement 还要求 `RUN_LIVE_ASR_BENCHMARK=1` 与
`RUN_ALIYUN_OSS_LIVE_E2E=1`。测试会上传两份 exact owned fixtures、生成 bounded signed URLs，并在 module
teardown 后断言 prefix 为空；不再手工粘贴 URL。只有 production selection 额外要求显式
`KVE_ASR_BENCHMARK_OWNER_APPROVED=1`。`RUN_LIVE_OPENAI_ASR=1 uv run --env-file .env pytest -q
tests/live/test_hosted_mini_asr_live.py` measures only the hosted route and never selects it.
The owner-approved configured-route gate is
`RUN_LIVE_OPENAI_ASR=1 uv run --env-file .env pytest -q -s
tests/evals/test_owner_approved_hosted_asr_live.py`.
Gate 不会因 credential 存在而自动开启。

The accelerated distributed capacity gates are:

```bash
KVE_TEST_REDIS_URI=redis://127.0.0.1:19736/15 \
  uv run pytest -q -s tests/integration/events/test_provider_limits_real_redis.py

KVE_TEST_REDIS_URI=redis://127.0.0.1:19736/15 \
  uv run pytest -q -s tests/integration/capacity/test_video_day_replay.py
```

The 5,000 replay uses cached providers and accounts 15,000 audio-minutes; it is
control-plane and cleanup evidence, not a claim that the 3-RPM hosted route can
finish the wall-clock workload.

x86_64 local-primary image:

```bash
export GH_TOKEN="$(gh auth token)"
docker build --secret id=gh_token,env=GH_TOKEN \
  --platform linux/amd64 -f Dockerfile.paddle \
  -t keble-video-enrichment:paddle-local .
```

Baked local-ASR candidate image:

```bash
export GH_TOKEN="$(gh auth token)"
docker build --secret id=gh_token,env=GH_TOKEN -f Dockerfile.whisper \
  -t keble-video-enrichment:whisper-local .
```

The BuildKit secret is required only while `uv` resolves the exact private
`keble-db` release-candidate commit. The Dockerfiles remove Git and its temporary
authorization header in the same layer; the token is not copied into the image.

`Dockerfile.whisper` pins the MIT CTranslate2 conversion
`dropbox-dash/faster-whisper-large-v3-turbo` at immutable revision
`0a363e9161cbc7ed1431c9597a8ceaf0c4f78fcf`. Build time downloads the six approved files,
writes their SHA-256 manifest, verifies it, and makes `/opt/faster-whisper` read-only. Startup
rehashes the artifact set before loading with `local_files_only=True`; runtime never downloads
weights. This is the independently measurable local-ASR candidate, not an automatic production
winner and not a replacement for the PP-OCR mass-volume image.

Apple Silicon host 可运行 unit 与真实 macOS local canary，但 Paddle 3.3.0 没有 Linux arm64 PyPI wheel；
因此 production image 必须在 x86_64 builder 构建，或由 CI 使用 `--platform linux/amd64` 构建。
历史 base/Paddle ASR-composition images 为 `sha256:36ce878d...` / `sha256:262bf83a...`。
当前 Mongo runtime candidate 是从 exact `9db27c8` lock 重建的 Paddle image
`sha256:eae43a9a8faec480d35986d9c9e77f08036233e8ee5ed42443ed3dbb6be1d8ca`；它以
non-root `kve` 运行、不含 Git 或 `/root/.gitconfig`，并已通过真实 Job `POST -> GET`。
Paddle image 的 Docker uncompressed size 为约 `3.18 GB`（Paddle runtime + OpenCV + ffmpeg）；这是
部署/cold-start 成本，不得把“provider call 为 0”误解成“总成本为 0”。正式放量前仍需比较常驻 worker
amortization、smaller inference runtime 和 hosted OCR 的总拥有成本。

旧 image digest 是 ASR route 组合 smoke 的历史证据，不是最终 Mongo runtime candidate：完整服务链证明
registry `keble-db 1.8.1` 会把 BSON datetime 解码为 naive 值并令严格 Job 读取失败。发布 `keble-db 1.9.0`
之前，`pyproject.toml`/`uv.lock` 必须固定 pushed UTC/cleanup checkpoint `9db27c8`；所有后续 production image
必须从该 lock 重建并通过真实 Job `POST -> GET`，禁止仅以 `/health` 代替持久化读取证明。

状态权威表：
[Master Plan §24](docs/plans/keble-video-enrichment-master-plan.md)。

## 合规 gate

授权（OD-00）通过前：禁止下载/处理/保存任何真实第三方视频；允许契约、文档、本地合成视频、
脱敏 fixture、单元测试与本地 HTTP server 集成测试。
详见 [架构文档 §10](docs/architecture/keble-video-enrichment-architecture.md) 与
[open-decisions](docs/decisions/open-decisions.md)。

## 文档入口

- [Master Plan](docs/plans/keble-video-enrichment-master-plan.md)（总入口）
- [Contract 权威定义](docs/contracts/enrichment-contracts.md)
- [架构文档](docs/architecture/keble-video-enrichment-architecture.md)
- [ADR-001 ～ 008](docs/adr/ADR-001-independent-video-enrichment-project.md)
- [审计报告（data-infra / tiktok / 基础设施 / Media2Cloud）](docs/references/keble-data-infra-audit.md)
- [Open Decisions](docs/decisions/open-decisions.md)
- [Current Handoff](docs/handoffs/current-handoff.md)

## Composer 执行入口

1. 读 [composer-execution-guide.md](docs/plans/composer-execution-guide.md)；
2. Phase 07 应用开发已完成；外部验证入口见
   [Phase 07](docs/plans/phase-07-production-readiness.md) 和
   [Operations Runbook](docs/operations/runbook.md)。

## 当前是否允许进入编码阶段

**Phase 05/06 已完成；Phase 07 application development 已完成；OD-01/OD-02/OD-06 均为
`DECIDED`。** Data Platform 双入口保持 owner-approved、DEFERRED、NON-BLOCKING。剩余工作是 gated
Aliyun OSS、生产 Mongo/Redis/provider 和目标服务器部署验证，不是未完成的应用编码。
