Skip to content
Merged
39 changes: 39 additions & 0 deletions GOALS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# 本地音视频上传:项目路线图

## 项目目标

自己及四五位可信朋友在现有网页选择本地音视频,复用既有转录、校对、总结、阅读与通知。成果公开阅读,变更由 owner 执行;默认保留30天,每次可显式选择 never。采用既有用户、HTTP/SQLite/worker、浏览器与通知路径,不新增账户、网盘、批量续传、broker、重试或外部撤销平台。

## 完成定义

- **软件交付**:后台、上传网页、本人历史、公开分享与停止分享、真实全链路行为验证和容量/恢复工具均落盘,经各自本地漏斗、正式CI/模型门禁与正常PR合并。
- **生产启用**:软件交付不自动启用;仍需真实容量/四正有限额度、代理、独立恢复控制及完整恢复域、撤销不复活/有效成果保护、实际消费环境证据和用户部署授权。未知保持 off。
- 索引中的 M1–M3「已完成」指已验收并合并的软件里程碑,不冒称生产证明完整。M1 历史未勾项不追填绿:A 曾经 primary failure,经真实反证/正规签票和 READY 后合并,不能改称原 primary success;当前跨进程/门禁与消费者回归由已合 B/C 证据继续覆盖。完整生产证明仍由 M6 承接。
- M6 当前只是非生产软件准备,工具及演练未实现;其软件前置 M1–M3 与最终验收/生产启用前置 M4–M5 及所有生产证明明确分开,不削减功能或提前标 done。

<!-- GOALS-INDEX:BEGIN generated by scripts/goals/goals_index.py -- do not edit by hand -->
| Lane | 激活里程碑 | 推进文件 |
| --- | --- | --- |
| local-upload | M6 | [goals/local-upload/M6-recovery.md](goals/local-upload/M6-recovery.md) |

| Lane | ID | 名称 | 状态 | 排序 | 优先级 | 依赖 | 进度文件 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| local-upload | M1 | local-upload/M1:上传身份、分享资格与清理基线 | 已完成 | 1 | 高 | — | [goals/local-upload/M1-baseline.md](goals/local-upload/M1-baseline.md) |
| local-upload | M2 | local-upload/M2:认证接收与幂等回执 | 已完成 | 2 | 高 | local-upload/M1 | [goals/local-upload/M2-intake.md](goals/local-upload/M2-intake.md) |
| local-upload | M3 | local-upload/M3:本地输入进入既有转录 worker | 已完成 | 3 | 高 | local-upload/M1, local-upload/M2 | [goals/local-upload/M3-worker.md](goals/local-upload/M3-worker.md) |
| local-upload | M4 | local-upload/M4:上传、回执与本人历史网页 | 阻塞 | 4 | 中 | local-upload/M1, local-upload/M2, local-upload/M3 | [goals/local-upload/M4-web.md](goals/local-upload/M4-web.md) |
| local-upload | M5 | local-upload/M5:全链路安全与兼容性证明 | 未开始 | 5 | 高 | local-upload/M1, local-upload/M2, local-upload/M3, local-upload/M4 | [goals/local-upload/M5-proof.md](goals/local-upload/M5-proof.md) |
| local-upload | M6 | local-upload/M6:恢复域、单向撤销与容量恢复 | 进行中 | 6 | 高 | local-upload/M1, local-upload/M2, local-upload/M3 | [goals/local-upload/M6-recovery.md](goals/local-upload/M6-recovery.md) |
<!-- GOALS-INDEX:END -->

## 路线图审计

### local-upload — 2026-10-09 / PR207

- **层0:方案仍必要吗?** 是。已合后台和浏览器底座尚未提供完整上传网页,原产品目标与非目标未变。
- **里程碑真完成了吗?** M1/#201、M2/#203、M3/#207 的软件实现已经正常合入;C 的正式 CI producer 在准确 H2 返回完整 SUCCESS。M4 网页、M5 全链路和 M6 工具/生产证明仍未完成。执行器运输未知与成果代码验收分别保留,不篡原报告。
- **下一个目标还是对的吗?** 是。网页待属主 registry 的 has_ui 真登记;先准备不依赖网页的 F 软件工具,不能借它启用生产。
- **有没有漏掉的里程碑?** 无新增产品里程碑;现有 M4/M5 和 M6 原检查清单继续约束未完成事项。不能拿浏览器底座 U 当 M4 已交付。
- **新证据是否改变顺序?** 是。后台已合、网页测试登记仍阻塞;将 M6 软件准备依赖限定已合 M1–M3,完整验收/生产启用依旧等 M4/M5 与原真实容量恢复授权。不跨仓改登记、不跳浏览器测试 SOP。
- **done 的定义还成立吗?** 是。软件、行为测试、测试规范/环境证据、生产启用分别判定。真实 producer payload 与有牙断言才是证据,fixture 不代表生产容量或模型质量。
- **审计结论**:按独立路线审计 `docs/sessions/261008-local-upload/roadmap-after-C.md` 调整可逆调度依赖,不更改目标;M6 仅软件准备进行中,其余缺项与授权边界保留。
3 changes: 1 addition & 2 deletions config/config.example.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
"task_status_retention_days": 180, // task_status 表终态(success/failed)任务记录保留天数(每日定时清理),0 表示永久保留。注意:/view 链接依赖 task_status 行解析,本值不应短于 cache_retention_days,短于时按缓存保留期执行(钳制并记 warning);cache_retention_days 为 0(缓存永久保留)时本清理会被跳过
"audit_log_retention_days": 180, // 审计日志保留天数(每日定时清理 audit.db 超期记录),0 表示永久保留
"max_download_size_mb": 4096, // 单文件下载大小上限(MB),0 表示不限制,防止超长视频填满磁盘
"upload_limits": { // 未经容量实测的上传限制留 null;缺少任一正有限值时上传能力关闭
"upload_limits": { // 四项真实容量证明齐备前保持 null;样本/示例值不能作为生产额度
"max_file_mib": null,
"max_media_hours": null,
"receive_concurrency": null,
Expand Down Expand Up @@ -417,7 +417,6 @@
// ============================================================
"funasr_spk_server": {
"server_url": "ws://localhost:8767", // WebSocket 服务地址
"send_terms": false, // 发送术语热词;非空 terms 会绕过普通缓存,重复音频也会重新识别
"max_retries": 3, // 连接/未知异常的最大重试次数
"retry_delay": 5, // 重试间隔(秒),也是 queue_full 无 retry_after 时的兜底退避
"connection_timeout": 30, // 连接超时时间(秒)
Expand Down
2 changes: 2 additions & 0 deletions docker/docker-compose.deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ services:
container_name: video-transcript-api
environment:
- TZ=Asia/Shanghai
# .env 是部署域环境源,位于 data/config 卷外;恢复数据库不得覆盖此处的关闭开关。
# 缺失 VTA_UPLOADS_ENABLED 或值不是精确小写 true 时保持关闭。
env_file:
- .env
ports:
Expand Down
5 changes: 3 additions & 2 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ services:
container_name: video-transcript-api
environment:
- TZ=Asia/Shanghai
# 可观测 DSN 只经 env_file 注入 os.environ(不挂载、不进 git)。
# 缺 ops.env 也能起(SDK fail-open)。DSN 绝不写进 config.jsonc。
# 部署环境值只经 env_file 注入 os.environ,不随 config/data 卷恢复。
# VTA_UPLOADS_ENABLED 缺失、空值或非精确小写 true 时保持关闭;不得为诊断而开启。
# 可观测 DSN 也只经 env_file 注入;缺 ops.env 仍可启动,DSN 绝不写入 config.jsonc。
env_file:
- path: ops.env
required: false
Expand Down
8 changes: 6 additions & 2 deletions docker/ops.env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# 可观测性注入文件(示例)。真实 ops.env 只放在部署目录、绝不进 git。
#
# 用途:docker-compose.yml 通过 env_file 把这里的变量注入容器 os.environ,
# 供 zlx-ops-sdk 上报错误到 GlitchTip。DSN 绝不写进 config/config.jsonc。
# 用途:docker-compose.yml 通过 env_file 把这里的变量注入容器 os.environ。
# SENTRY_DSN 供 zlx-ops-sdk 上报错误;DSN 绝不写进 config/config.jsonc。
# VTA_UPLOADS_ENABLED 是部署域独立开关;保持空值/未设置即关闭,不能用此样例启用。
#
# 部署步骤(n305):
# 1. 在 GlitchTip 建 project 拿到 DSN
Expand All @@ -11,3 +12,6 @@

# GlitchTip DSN(缺省 = 不上报,SDK fail-open,不报错)
SENTRY_DSN=

# 本地上传必须经容量/恢复/部署授权后才能设置为精确的小写 true。
VTA_UPLOADS_ENABLED=
53 changes: 53 additions & 0 deletions docs/operations/local-upload.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# 本地上传运维检查与恢复边界

本说明只覆盖软件侧工具与安全操作顺序,不构成生产容量、恢复或启用证明。当前 M6 软件工具可以在本地沙箱演练;生产入口、磁盘/代理容量、ASR 最大工作集、独立恢复域与部署授权仍未知,必须保持上传开关关闭。

## 真实消费路径

- API 通过 `load_and_validate_config` 读取 `/app/config/config.jsonc`;Docker consumer 将宿主配置目录挂到该路径。四项 `storage.upload_limits` 必须为正有限值(并发为正整数);示例配置保留 `null`,不会把实验数字写入生产额度。
- `docker/docker-compose.yml` 的服务环境来自可选 `docker/ops.env`,数据与配置分别挂在 `../data`、`../config`。部署 compose 的环境源是部署域 `.env`,配置与数据挂载为 `./config`、`./data`;仓内 `docker/deploy_targets.json` 指向 `/opt/media/VideoTranscriptAPI`。
- `VTA_UPLOADS_ENABLED` 是进程环境变量,不是 JSON 配置,也不在数据卷里。上传接收实际要求值精确等于小写 `true`;未设置、空值及其他值均关闭。恢复 `data/` 不得覆盖部署域 `.env`。Resolver 与接收入口都要检查该 gate。
- 当前工具不读取 `.env`,只从 tracked compose 描述中报告 `env_file` 文件名。检查 `.env` 时仅可用单键 `grep`,不得 `source` 整个文件;不得打印完整环境或配置。

## 本地诊断

只对已知配置路径运行;不要把沙箱样例数写成生产额度:

```bash
uv run --frozen python scripts/ops/local_upload_check.py \
--config <consumer-config-path> \
--data-dir <consumer-data-mount> \
--compose <tracked-deploy-compose-path> \
--repo <source-checkout> \
--source-sha <artifact-attested-full-source-sha> \
--sample <known-local-media-file>
```

工具使用真实配置解析器、只读取配置中的有限额度字段、目标文件系统剩余空间及可选样本的 `ffprobe` 时长/大小;不会打印配置秘密、读 dotenv、启用/部署/恢复数据。兼容判据是 Git 对象中已存在的 commit ancestry,最低 A guard-only SHA 为 `6af391d20edab8dfd8b320ce6b91d4d19e3260e2`;更早的 pre-A `38b299468daaf309608f46ffe1c59886b0922c04` 应拒绝。 ancestry 只核验源码祖先,不证明 OCI digest 与源码 SHA 的绑定,也不证明真实旧 image 行为。

无论本地检查多绿,CLI 都以 `UPLOAD_ENABLE_BLOCKED` 结束:当前尚未提供生产四限额实测、独立恢复域证据及生产 ingress/image 证明。不可将 gate 设置为 true,也不能用该诊断输出代替部署授权。

### 有界本地接收实验

```bash
uv run --frozen python scripts/perf/local_upload_capacity.py --duration-seconds 20
```

命令会报告 source SHA 与工作树 clean/dirty 状态;只有 clean checkout 的观测才可归因到精确提交。它会自行新建并回收一个 OS `mkdtemp`,从 tracked 示例生成仅用于实验的配置,服务只绑定 loopback,数据、用户和日志都写该临时目录。脚本用原始 HTTP 向真实 FastAPI upload receiver 发送两份 20 秒、PCM 16-bit mono/48 kHz 合成 WAV,运行真实 `ffprobe` 与当前 worker;同时用受控 loopback HTTP fixture 做 URL 下载。ASR 对端被替换成 loopback WebSocket 拒绝器,worker 应显式失败,不调用外部 ASR/LLM/通知服务。脚本比对服务端 SQLite 中每份实际字节数和 SHA-256,并测量 10ms 采样间隔下临时目录观察峰值。

这是一个有明确大小/时长/并发上限的单机诊断,不测真实生产硬盘配额、反代缓冲、ASR/LLM 峰值与最大时长;观察峰值不是预留量,也不得回填四项限制。任何实验中的未知或异常都应中止并查因,不能扩限重跑直到变绿。

## 安全恢复顺序(仅获授权后)

1. 在部署项目根 `/opt/media/VideoTranscriptAPI` 只查询开关一键,不读/打印整个 `.env`:`grep -n '^VTA_UPLOADS_ENABLED=' .env`;缺键、空值或非精确 `true` 都是关闭态。检查返回非零时应区分“键不存在”与“值不同”。如果值为 true,须先由获准操作者在部署域将该单键改成 false,并重新 grep 核实;不得 `source .env`。
2. 服务保持停止:`docker compose -f docker-compose.yml --project-directory /opt/media/VideoTranscriptAPI --env-file /opt/media/VideoTranscriptAPI/.env stop video-transcript-api`。只用授权恢复工具把 cache DB 与对应正文文件恢复到已知空目标;严禁覆盖部署目录 `.env`、服务 unit 或其他卷。不得使用本卡工具自动恢复任何数据库。
3. 核对恢复来源确实包含撤销后的权威状态,并检查 DB 与正文/必要映射完整。仅有撤销前 snapshot 时,恢复会带回旧 `revoked_at=NULL`;本地行无法识别完整卷回退。没有独立、可信且可核验的撤销来源时,停止在关闭态,不可声称撤销未复活。
4. 仍保持环境 gate 关闭,启动隔离消费方做只读核对:真实 Resolver 拒绝被撤销记录,cache/task cleaner 保留有效 `never` 与有效 30d 正文及必要 root 映射,原媒体仍按既有临时清理规则处理。恢复后的服务只有在独立部署授权后才可启动;在证据齐全前不要把 gate 改回 true。本文测试只在 pytest `tmp_path` 沙箱验证,未启动 systemd 或生产服务。
5. 只有 M4/M5 完成、部署 image 具有可验证 source SHA、A guard-only 或更新版本通过真实消费回归、独立恢复域/撤销状态可证明、四限额在实际磁盘/ASR并发/URL负载下测得、真实代理/ingress 条件已核对且获得单独部署授权后,才可由授权操作者评估设置开关。任一项 unknown 就继续 off。

## 当前证据状态

- 已实现并测试:独立 SQLite+正文生产者生成 snapshot;撤销后将 sandbox 恢复到撤销前副本;fresh Resolver 通过独立 `VTA_UPLOADS_ENABLED=false` 子进程环境拒读;current cache/task cleaner 对有效长期成果及 root 保持保护。
- 已用真实 A guard-only SHA 的源树作为独立 subprocess consumer,消费当前 producer 生成的 SQLite+文件;启用 sandbox gate 时读取合法 active share,legacy upload-token/blank token 别名拒绝,两个 cleaner 均保留旧龄有效长期结果。pre-A 版本由 commit ancestry 判据拒绝。
- 本机单次 synthetic 探针数据只代表当次硬件、依赖与临时文件系统;ASR 被 loopback rejector 拒绝,ASR 容量、代理缓冲与真实生产并发未知。脚本报告为 blocked,不将该样本作为任何生产额度。
- 未执行:真实生产 ingress、配置/数据卷、systemd 身份与 mount、生产备份恢复、真实磁盘配额、真实 ASR/LLM 工作集或用户部署授权。不得把未执行改写成 passed。
51 changes: 51 additions & 0 deletions docs/sessions/261008-local-upload/roadmap-after-C.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# local-upload:C 合并后的路线审计

审计基线:主干 `0ae4bffe911fa93bb858d24fd173aee602af9c0b`。本文件只同步既定规划和调度,不评审新产品、不改应用、不触碰真实服务;目标和非目标保持不变。

## 层 0:原方案是否仍必要?

**仍然必要,方案不变。** 本地上传仍沿用既有处理链,正文分享身份与任务进度分离;默认 30 天,长期分享必须显式选择 `never`;公开读取、owner 修改。部署、生产容量与恢复仍需另行授权。U 的浏览器底座已合并不等于网页 M4 完成,C 的代码与测试也不构成生产质量、容量或启用承诺。

## 五问

### 1. M3 真的完成了吗?

**实现阶段已完成并合入。** M3 对应 PR #207,merge commit 为 `0ae4bffe911fa93bb858d24fd173aee602af9c0b`;C producer commit `ef425d77cdaecb693aba76ca6cb3f847ad5196e5` 的 run `37864605980`、attempt 1 真实 run conclusion 为 `success`,`gate / primary` job conclusion 也为 `success`。独立 review 记录 `96cd595097cf549fa058d2e3a9fefd48b99ca9ea` 只判代码,不承诺生产质量或容量。PR #201/#203/#207 的合并与里程碑编号分别记于 M1/M2/M3;#202 未合并,不用其 owner 字段推断实现归属。

### 2. 下一个目标是什么?

**先做 F/M6 的非生产软件工具准备。** M1、M2、M3 已合并,原 F 卡允许从 A/C 后准备软件工具和临时 fixture,不需要等待网页 M4/M5 才开始规划或实现这部分。当前仅拆清了依赖;恢复工具、脚本与 fixture 尚未实现,不能记为通过。

D 仍等待 UI registry 登记:Gate Hub #1393 最新状态为 OPEN,尚无 `has_ui: true`。按 SOP 必须先登记再派 UI;不得跨仓代改。登记完成后才推进 D/M4 网页。

### 3. 还漏了什么?

- M4 网页尚未开始,且受 UI registry 登记阻塞;浏览器底座 PR #200 不等于上传网页、真实 API producer 或历史行为验收。
- M5 全链路行为证明尚未开始,依赖 M4;真实 HTTP producer 到正文、任务、Resolver、history 与 cleanup 的完整证据仍未取得。
- M6 软件工具和临时 fixture 尚未实现;完整恢复演练、撤销不复活、有效正文保护、四项真实限额测量、实际消费环境证据与部署授权都未完成。
- 不以已合代码、review、CI、mock/helper 测试或文档代替上述真实行为与生产证据;生产开关继续关闭。

### 4. 顺序是什么?

1. 现在可先推进 F/M6 非生产软件工具准备(前置为 M1/M2/M3);不操作生产服务。
2. D 等待 Gate Hub 完成 `has_ui: true` 登记后再派发;随后实现并验收 M4。
3. M4 完成后推进 M5 的全链路行为与安全证明。
4. M6 完整验收与生产启用评估必须等待 M4、M5,并补齐真实独立恢复域/控制、撤销状态不复活、有效正文保护、四项限额实测、消费环境证据及用户/部署授权。

M6 的 frontmatter `depends_on` 仅表示可先启动的软件准备前置 M1/M2/M3;它不删除或缩减第 4 步的完整验收和生产启用前置。

### 5. 完成定义是什么?

分开记录四种完成,不能互相替代:

- **软件准备**:非生产恢复工具/脚本与临时 fixture 真正实现,并由可复现的本地验证证明;目前尚未实现。
- **行为测试**:M4 使用真实浏览器/API producer 锁定字节、headers、receipt、历史与状态;M5 再用真实 HTTP、SQLite、文件、dispatcher、Resolver 和消费者完成全链路证明。fixture 必须保留真实 producer 输出,跨边界断言真实 payload;目前 M4/M5 未完成。
- **SOP / 环境证据**:先完成 UI registry 登记和派发 SOP;M6 还需在获授权的隔离真实 unit/身份/mount/数据格式下验证环境传递及完整恢复过程。文档或模拟介质不算已执行。
- **生产启用**:仅当 M4/M5 与 M6 全部证据齐全、独立审查通过、四项限额在真实环境实测、有效正文保护与撤销不复活得到证明,并获得用户/部署授权后才可评估;任何一项缺失都保持默认关闭。软件准备完成不会自动启用。

## 实际状态及核实边界

- GitHub PR #201、#203、#207 均为 MERGED,merge commits 分别为 `6af391d20edab8dfd8b320ce6b91d4d19e3260e2`、`c7b38563eb2f06f5ca36c06c6c7da7de9c5e9995`、`0ae4bffe911fa93bb858d24fd173aee602af9c0b`;当前 GitHub `main` 指向最后一个 SHA。
- M1/M2/M3 的 `merged_pr` 与状态已同步为 #201/#203/#207;owner 表示后续推进责任,不是实现者归因。M4 阻塞、M5 未开始,均未标完成。M6 仅软件准备进入规划/进行中,恢复脚本及 fixture 尚未落地。
- 未核验或未获得的生产证据仍未完成:真实生产服务部署、容量、完整恢复域与授权。本次没有访问/更改真实配置、数据、服务或生产网络。
- 仓内原不存在本路线文件;本次在任务卡指定的精确路径新建,没有另造 Goal,也未写入 `GOALS.md`。派生表留给 lead 后续生成。
Loading
Loading