Merge remote-tracking branch 'origin/master' into fix/web-probe-memory-guard

This commit is contained in:
Codex
2026-07-13 10:30:15 +02:00
32 changed files with 1538 additions and 414 deletions
@@ -45,6 +45,12 @@ bun scripts/cli.ts agentrun create task \
- 未显式传 `--idempotency-key` 时,由 Target/repo/ref/MDTODO/verified commit 派生稳定键;
- 幂等重试遇到已有 running 或 terminal task 时,返回既有 attempt/run/session,不重复 dispatch
- 最终输出必须包含 task、attempt、run、command、runner 和 session identity。
- Target live 的默认 human、`-o json``-o yaml` 使用同一份有界投影:
- 保留 task、attempt、run、command、runner、session 的 identity 与 state
- 保留自动派单决策、Target/MDTODO 摘要、Aipod 与 SecretRef presence 摘要;
- `Next` 使用本次派单的具体 identity,并继承 Target 与 lane
- 不展开完整 task、prompt、payload 或 Aipod 对象;
- 需要完整服务端对象时显式使用 `--raw`
Artificer 内部执行边界:
+17 -2
View File
@@ -9,9 +9,24 @@ HWLAB Cloud M1 / UniDesk / AgentRun 跨仓项目治理锚定在 UniDesk 仓库
## 高频规则
- 项目管理 stable source of truth `project-management/PJ2026-01/`GitHub issue 承载执行流和证据索引。
- 项目管理稳定事实源按项目编号维护`project-management/PJ*/`GitHub issue 承载执行流和证据索引。
- 重大项目/方向/课题/子课题层级、任务书、实施方案、测试规格、阶段报告和项目偏离按项目编号目录维护。
- 形成多阶段实施、架构或长期 API/数据模型时,先 SPEC,再实现和 issue 阶段计划。
- SPEC 规范性正文只定义预期终态:
- 目标能力;
- 稳定边界;
- 目标架构、数据流和接口;
- 原子需求;
- 验收契约。
- SPEC 不得写入执行态内容:
- 当前实现状态或能力基线;
- 已完成、未完成和当前阻塞;
- 临时运行面、本轮差距和一次性证据;
- 源码路径、作业编号、日志、截图或样片清单。
- 规格生命周期状态与实现进度必须分离:
- 文档控制可以记录规格已生效、未生效或已废弃;
- 不得在 SPEC 中增加“实现状态”字段或用目标需求宣称能力已经上线。
- 当前状态进入阶段报告、任务报告、偏离记录或 GitHub issue;验证证据只从这些执行态载体引用 SPEC,不反向写入 SPEC 正文。
- GitHub issue/PR 写入仍走 `$unidesk-gh`;正文必须写目标合并分支/lane。
## Reference 路由
@@ -22,6 +37,6 @@ HWLAB Cloud M1 / UniDesk / AgentRun 跨仓项目治理锚定在 UniDesk 仓库
## 常用检查
```bash
find project-management/PJ2026-01 -maxdepth 3 -type f | sort
find "project-management/${PJ_ID:?请先设置 PJ_ID}" -maxdepth 3 -type f | sort
bun scripts/cli.ts gh issue list --repo pikasTech/unidesk --state open --limit 30
```
+2 -2
View File
@@ -1,4 +1,4 @@
interface:
display_name: "UniDesk OA"
short_description: "管理 UniDesk 项目规格目录和 issue 层级。"
default_prompt: "使用本技能规划 project-management/PJ2026-01 项目结构、总规格和相互链接的 GitHub issue 执行记录。"
short_description: "管理 UniDesk 项目终态规格、层级与执行态报告"
default_prompt: "使用 $unidesk-oa 为指定项目编号维护预期终态 SPEC,并把当前状态放入报告或 issue。"
@@ -20,6 +20,19 @@ HWLAB Cloud M1 需要一个项目控制结构,避免局部 issue/PR 工作偏
- 决策依据。
- 稳定验收标准。
SPEC 只承载预期终态:
- 目标能力和稳定责任边界。
- 目标架构、数据流、接口和原子需求。
- 可重复执行的验收契约。
- 规格生命周期状态可以记录已生效、未生效或已废弃,但不等于实现进度。
SPEC 不承载执行态:
- 当前实现状态、阶段基线和本轮差距。
- 已完成、未完成、当前阻塞和临时运行面。
- 源码路径、commit、PR、作业、日志、截图、样片或一次性验收证据。
用 issue 承载执行状态:
- 当前负责人和状态。
@@ -30,11 +43,16 @@ HWLAB Cloud M1 需要一个项目控制结构,避免局部 issue/PR 工作偏
- 收口评论。
- 长证据索引、CaseRun registry、运行日志、历史 issue 摘要。
不要把稳定需求的唯一副本放在 issue 评论里。长期有效的需求必须蒸馏到 `project-management/PJ2026-01`,再从 issue 链接过去
阶段报告、任务报告和偏离记录承载跨 issue 的状态汇总。不得为了保存执行进度,把这些内容回填到 SPEC 规范性正文
不要把稳定需求的唯一副本放在 issue 评论里。长期有效的需求必须蒸馏到对应 `project-management/PJ*/` 项目编号目录,再从 issue 链接过去。
不要把长证据正文放进项目管理目录。项目管理目录只承载规格、治理和阶段中心;长证据保留在 GitHub issue。
规格文件和 GitHub issue 的引用规则分开处理:
- `project-management/PJ2026-01/specs/*.md` 不保留单独的迁移来源块;历史来源只写在修改历史 `v0.1` 变更说明中,格式为 `迁移来源 <owner>/<repo>#<number>`
- 规格文件不保留单独的迁移来源块:
- 范围是 `project-management/PJ*/specs/*.md`
- 历史来源只写在修改历史 `v0.1` 变更说明中;
- 格式为 `迁移来源 <owner>/<repo>#<number>`
- 规格文件引用其他规格时,使用同目录相对路径 Markdown 链接,例如 `[PJ2026-0101 硬件池](PJ2026-0101-hardware-pool.md)`;不要引用其他规格的 GitHub issue、证据 issue、PR 或裸 `#<number>`
- GitHub issue/PR 正文和评论中的 issue/PR 引用必须写成 `[#<number>](https://github.com/<owner>/<repo>/issues/<number>)``[#<number>](https://github.com/<owner>/<repo>/pull/<number>)`,显示短号、链接目标保留完整 URL;不要显示裸长链接、裸井号编号或 `owner/repo` 加井号编号。`owner/repo#number` 只允许作为 CLI 命令参数 shorthand。
@@ -48,6 +66,7 @@ P0 SPEC 必须完成:
- 按模板写清文档控制、目的和范围、术语表、系统边界、内部分工、原子需求和过程控制。
- 为涉及实现架构的数据面补齐目标架构图、数据流图和关键时序图;图形优先使用 Markdown 内嵌 `mermaid`
- 在 SPEC 中写明后续代码文件头部应标注的 SPEC 编号、短名和实现引用版本。
- 把正文写成预期终态,不以当前实现、阶段基线或未完成清单解释目标。
后续代码阶段只能在 P0 SPEC 明确后开始。后续讨论改变稳定需求、数据流、接口或验收口径时,先更新 SPEC,再更新 issue 计划。该规划型 issue 范围内新增或修改的源码文件,文件头部必须标注遵循的 SPEC 编号、短名和实现引用版本;自动生成文件、第三方 vendored 文件、纯配置、锁文件和不能承载注释头的二进制产物可例外,但对应生成器、渲染器或配置入口必须能追溯到 SPEC。
@@ -103,15 +122,15 @@ specs/spec-governance.md # 编号、层级、回写与偏离规
| 层级 | 负责 | 不负责 | 回写到 |
| --- | --- | --- | --- |
| L0 | 使命、非目标、当前中心、L1 方向树、全局验收、偏离规则 | PR 细节、CaseRun 日志、日报流水、实现设计 | `project-management/PJ2026-01/specs/PJ2026-01-HWLAB.md` 和 L1 issue |
| L0 | 使命、非目标、预期终态、方向树、全局验收 | 当前中心、PR/CaseRun/日报与实现设计 | L0 规格和 L1 issue |
| L1 | 能力域范围、成功标准、L2 课题清单、原始验收类型 | 单个 PR、一次性 smoke、仓库/工具/运行面名称、项目管理动作 | L0 规格和对应 L1 规格文件 |
| L2 | 一个 L1 内的具体课题、交付物、阻塞项、验证计划 | 宽泛能力域或实现细节流水 | L1 issue,重大移动再回写 L0 |
| L2 | 一个 L1 内的具体课题、目标交付物验证计划 | 当前阻塞、宽泛能力域或实现细节流水 | L1 issue,重大移动再回写 L0 |
| L3 | 一个有界验收切片和单一验收路径 | 路线图、多课题计划、父级范围变化 | L2/L1 issue 并带证据 |
| L4 | 具体执行:PR、CaseRun、smoke、部署、文档收口 | 新需求、新方向、验收规则变化 | 最近的 L3/L2/L1,并带原入口证据 |
分类规则:
- 改变使命、当前中心、L1 列表或全局验收的,属于 L0。
- 改变使命、预期终态、L1 列表或全局验收的,属于 L0。
- 定义可以容纳多个课题的稳定能力域的,属于 L1。
- 在一个方向内规划具体工作计划的,属于 L2。
- 可以通过一个有界路径验收的,属于 L3。
@@ -122,7 +141,7 @@ specs/spec-governance.md # 编号、层级、回写与偏离规
L0 定义:
- 使命和非目标。
- 当前工作重心
- 预期终态
- 产品入口。
- 方向列表。
- 全局验收标准。
@@ -139,10 +158,10 @@ L1 方向是直接服务 L0 使命的顶层能力域,不是项目管理工件
每个方向必须回答:
- 它服务 L0 目标的哪一部分。
- 什么态算完成。
- 什么态算完成。
- 涉及哪些仓库、分支、lane。
- 哪个原始用户/运行面入口可以验收它。
- 当前重要的 L2 课题有哪些。
- 目标 L2 课题有哪些。
以下内容不能作为 L1 方向:
@@ -158,7 +177,7 @@ L1 方向是直接服务 L0 使命的顶层能力域,不是项目管理工件
- 主责不清楚时,问“什么证据能关闭这个 issue”:硬件资源就绪、Agent 执行生命周期、Harness/RL 证据、客户端/API 行为、用户/管理策略,还是平台交付/运维。
- 先按完成标准判断主责,再看实现表面。CLI、Web 页面、HTTP API、hwpod 工具或服务路由不会自动归入 `客户端`;它们主归属定义正确性的领域。只有当完成标准是 Web、CLI、HTTP API 的入口行为、用户交互、兼容性或输出格式时,`客户端` 才是主责 L1。
HWLAB Cloud M1 当前 L1 职责矩阵:
HWLAB Cloud M1 L1 职责矩阵:
| 编号 | 短名 | 主责 | 不负责 | 交接示例 |
| --- | --- | --- | --- | --- |
@@ -189,7 +208,7 @@ HWLAB Cloud M1 当前 L1 职责矩阵:
### 硬件池 L2 标准
`PJ2026-0101 硬件池` 当前 L2 拆分为:
`PJ2026-0101 硬件池` L2 拆分为:
| 编号 | 短名 | 主责 | 关联 L1 | 边界 |
| --- | --- | --- | --- | --- |
@@ -1,5 +1,13 @@
# UniDesk OA 模板
## SPEC 正文边界
- SPEC 只写预期终态、目标能力、稳定边界和验收契约。
- 文档控制中的“状态”只表示规格生命周期,不表示实现进度。
- SPEC 不增加“实现状态”“当前基线”“当前阻塞”或“已完成能力”章节。
- 当前状态、阶段差距和实现证据进入阶段报告、任务报告、偏离记录或 GitHub issue。
- 源码路径、commit、PR、作业、日志、截图和样片清单不得作为 SPEC 规范性正文。
## L0 总规格骨架
```markdown
@@ -9,9 +17,9 @@
| 版本 | 对应 commit id | 更新日期 | 变更说明 |
| --- | --- | --- | --- |
| v0.1 | <commit> | <YYYY-MM-DD> | 创建需求规格;如来自历史 issue,只在这里写迁移来源 <owner>/<repo>#<number>。 |
| v0.1 | <commit> | <YYYY-MM-DD> | 创建预期终态需求规格;如来自历史 issue,只在这里写迁移来源 <owner>/<repo>#<number>。 |
当前正文仍在规格治理草稿中;未定稿前不新增版本号,不为单次编辑追加 `待提交` 版本
修改历史只记录规格语义变更,不记录实现进度、阶段基线或一次性证据
## 正文
@@ -24,7 +32,7 @@
| 编号 | PJ2026-01 |
| 短名 | HWLAB |
| 层级 | L0 总项目 |
| 状态 | 已生效、已废弃或未生效 |
| 规格状态 | 已生效、已废弃或未生效;只表示规格生命周期 |
| 需求规格模板 | [ISO/IEC/IEEE 29148 需求规格模板](../../templates/iso-iec-ieee-29148-requirements-spec-template.md) |
| 规格治理索引 | [规格治理](spec-governance.md) |
+24
View File
@@ -17,6 +17,10 @@ description: UniDesk 主代理调度子代理的必读技能。用户提到子
- 执行型和调研型委派默认优先使用 AgentRun `Artificer`
- Artificer 的 `create task``apply``dispatch` 必须以下文“子 issue + MDTODO”登记为 fail-closed 前置;禁止先创建 task 或派单再补登记;
- 原生子代理和 Artificer 每次派单前都必须已有对应 MDTODO ITEM 或 SUBITEM;派单 prompt 与最终报告必须写明同一个 MDTODO ID;
- Artificer 协调状态只复用 MDTODO 与 AgentRun 已有的 task/run/command/session 资源:
- 普通派单、续跑、重试和 closeout 不得另建锁、租约、证明链、第二状态库或配套围栏;
- 审查证据只保留 issue/PR/commit、验证摘要和必要下钻链接,不复制无界日志或构造额外实证体系;
- 只有直接保护真实业务资源、不可逆操作或明确损害风险时才允许最小 guard,且只覆盖实际风险窗口并在风险解除后立即释放;
- Artificer 的 `create task` 外层入口:
- 必须按 owning YAML 选中 Target
- 必须通过 `trans <Target>:<unideskWorkspace>` 重入目标节点;
@@ -64,6 +68,26 @@ description: UniDesk 主代理调度子代理的必读技能。用户提到子
- 子代理完成后必须留下可审查工件:PR、issue comment、commit、验证输出摘要、部署/observer/trace 证据或阻塞说明;主代理不能只凭口头结论合并。
- 子代理长时间无响应时,主代理按“问询 -> 只读检查 worktree/PR/issue -> 再窄问询”的顺序处理;多次问询仍无回复时,可以关闭旧子代理,并在原 worktree/原分支/原 issue 边界上重开新子代理接续。重开前不得删除或重置旧 worktree;新子代理必须先读取原 worktree 状态和既有 issue/PR/comment 链接,再继续最小下一步。
- 子代理完成任务后,主代理必须再给该子代理发送 post-task 收口要求;若主代理要求子代理纠偏或补验证,则等纠偏完成后再发 post-task。post-task 反馈由子代理按 `$post-task` 自行给出判断,主代理不负责反馈池去重;主代理只从子代理提好的反馈中挑选适合工程化的项转成正式 FEATURE/BUG issue,并优先派回提出该反馈的子代理执行。
- 主代理结束条件:
- 当前用户任务只要派出过子代理:
- 主代理就必须保持任务进行中;
- 等待本轮全部子代理进入真实终态;
- 不得把 task 创建成功、dispatch 成功、`running`、已提交 commit 或已创建 PR 当作主代理可以结束的交付点。
- 子代理返回后,主代理必须继续完成职责范围内的收尾:
- 完成必要的纠偏和 post-task
- 审核 PR 并执行获授权的合并;
- 同步目标分支;
- 完成任务要求的自动交付、线上验证或原入口复测。
- 主代理还必须完成治理收尾:
- 完成对应 issue closeout
- 写入 MDTODO 任务报告并更新状态;
- 清理已吸收的 worktree 和分支;
- 确认没有本轮子代理遗留的必要收尾后,才可以向用户结束当前任务。
- 不得以“异步派发”“非阻塞支线”或“后续再观察”为由提前结束。
- 只有满足以下任一条件时才可停止等待:
- 用户明确取消或暂停;
- 出现需要用户新增授权且无法继续的外部阻塞。
- 因取消、暂停或外部阻塞停止等待时,不得把任务报告为已完成。
## 主线与支线隔离
@@ -40,6 +40,7 @@
- 再使用 `$mdtodo-edit` 在泛化问题域 MDTODO 中创建或更新 ITEM/SUBITEM、登记子 issue 链接并标记进行中;
- 只有上述写入成功后,才允许 AgentRun `create task``apply``dispatch`
- 缺少 MDTODO FILE、任务项、进行中状态或子 issue 链接时不得派单,派单后补写不算合规。
- 派单后的协调状态直接复用 MDTODO 与 AgentRun task/run/command/session;普通续跑、重试和 closeout 不新增锁、租约、第二状态库、证明链或围栏。证据保持为有界摘要与稳定链接;只有直接保护业务资源、不可逆操作或明确损害风险的最小 guard 才属于例外。
- 主 issue 评论区由主代理独占维护:只写主线 anchor、阶段汇总、调度决策、已采纳结论、下一批边界和最终 closeout。子代理不得直接在主 issue 评论区堆过程、日志、单步证据或 post-task 反馈;需要让主线可见时,由主代理在主 issue 引用子 issue/PR/comment 链接。
- 主代理派发执行型子代理前必须先创建子 issue,不能把主要任务正文直接塞进 subagent prompt。子 issue 标题应能反映父 issue、运行面/模块和子任务;正文必须引用父 issue、目标分支/worktree、允许范围、禁止范围、验收入口、模型/思考等级选择理由和当前接续链接。子代理的调查、单步证据、阻塞、post-task 和后续接力评论都写在自己的子 issue 或关联 PR 中。
- 子代理 prompt 只传子 issue 链接、模型要求和“不写父 issue 评论区”等极短边界;不要在 prompt 里复述长任务、历史结论、日志或完整证据。主代理通过观察子 issue 评论区跟踪进度。
+23 -1
View File
@@ -83,10 +83,32 @@
由原生子代理在最新远端基线的独立 worktree 实现易用派单入口:外层在 YAML Target 上通过 trans 派单,Artificer 内部通过 trans 到 NC01 目标工作区复用主机权限;覆盖 target/target-workspace/repo/ref、创建前源码身份预检、合法 session/workspaceRef/Aipod 投影、owning prompt,以及 unidesk-subagent 的“所有子代理先建 MDTODO”固定规则;对应原生任务 artificer_dispatch_ux,完成任务后将详细报告写入[任务报告](./details/artificer-runtime-capabilities/R6.1_Task_Report.md)。
### R6.2
### R6.2 [in_progress]
主代理审查跨仓改动并按依赖顺序合并 PR,只观察自动 CI/CD 收敛;不得人工触发 mirror、PipelineRun、apply 或 rollout,完成任务后将详细报告写入[任务报告](./details/artificer-runtime-capabilities/R6.2_Task_Report.md)。
### R6.3
上线后通过 trans 在 NC01 使用新单命令入口,以任务 worktree /root/.worktrees/selfmedia/r6-ocr-visual-planner、仓库 pikainc/selfmedia 和 ref feat/r6-ocr-visual-planner 派发 OCR/视觉规划,确认 task 直接 running、Artificer 内部继续用 trans、session 可续跑且不出现 schema-invalid 或 repository-not-found,完成任务后将详细报告写入[任务报告](./details/artificer-runtime-capabilities/R6.3_Task_Report.md)。
### R6.4 [in_progress]
修复 Target 单命令 live 输出超过全局预算后丢失 task/run/session identity:默认 human 与显式 JSON/YAML 都先返回紧凑 identity、auto-dispatch decision、Target 摘要与下钻命令,不嵌入完整 task/Aipod payload;补超过 10 KiB 场景测试,确保不会退化为 stdout-text-bytes-exceeded-threshold,完成任务后将详细报告写入[任务报告](./details/artificer-runtime-capabilities/R6.4_Task_Report.md)。
### R6.5 [in_progress]
修复 cancelled task 后同 session continuation 未继承原 session 的 tenant/project,导致 send session 被 tenant-policy-denied 拒绝;由 session/task durable identity 自动沿用原 project,不绕过策略,并用 selfmedia 原 session 续跑 canary 验收,完成任务后将详细报告写入[任务报告](./details/artificer-runtime-capabilities/R6.5_Task_Report.md)。
## R7 [completed]
解决 [AgentRun #348](https://github.com/pikasTech/agentrun/issues/348):由 owning Artificer Aipod YAML 把单 turn hard-timeout 从 30 分钟调整为 2 小时(7200000 ms),只改 YAML 及最小必要 render/配置测试,不新增代码默认、第二 timeout authority、客户端续跑补丁、合同、租约、安全机制或围栏,完成任务后将详细报告写入[任务报告](./details/artificer-runtime-capabilities/R7_Task_Report.md)。
### R7.1 [completed]
由原生子代理按 [AgentRun #348](https://github.com/pikasTech/agentrun/issues/348) 在独立 v0.2 worktree 修改 config/aipods/artificer.yaml timeoutMs 为 7200000,并完成最小 render/配置测试;禁止 Artificer 递归修自己,完成任务后将详细报告写入[任务报告](./details/artificer-runtime-capabilities/R7.1_Task_Report.md)。
### R7.2 [completed]
主代理审查并合并 [AgentRun #348](https://github.com/pikasTech/agentrun/issues/348) 的最小 PR,只观察自动 CI/CD,不人工触发交付链,完成任务后将详细报告写入[任务报告](./details/artificer-runtime-capabilities/R7.2_Task_Report.md)。
### R7.3 [completed]
自动上线后以新 Artificer task input 与超过 30 分钟的真实或受控 canary 验证 2 小时 budget 生效,不修改当前 Monitor run 的既有 task policy,完成任务后将详细报告写入[任务报告](./details/artificer-runtime-capabilities/R7.3_Task_Report.md)。
@@ -138,3 +138,7 @@
### R9.1
按 [UniDesk #1905](https://github.com/pikasTech/unidesk/issues/1905) 清理冲突的 reference/skill/Next,并在 Artificer YAML prompt/resource bundle 内明确支持 stdin 时禁止一次性 /tmp 正文;补最小 render/行为测试后独立交付,当前只登记不执行,完成任务后将详细报告写入[任务报告](./details/cli-output-progressive-disclosure/R9.1_Task_Report.md)。
## R10 [in_progress]
解决 [UniDesk #1656](https://github.com/pikasTech/unidesk/issues/1656):由 Artificer 在独立 `.worktree/cicd-status-visibility``fix/cicd-status-visibility` 分支修复 `cicd status --node` 成功零输出,并统一 PaC consumer 的 registry applicabilityGitOps-only/publisher-only 应显示 N/A 且由 PipelineRun、GitOps/Argo 与 runtime 证据闭环,只有 owning YAML 明确声明 image/registry 的 consumer 才把 registry missing 作为 blocker。保持默认输出有界、text/JSON/YAML 同构、缺失可选 YAML 事实降级为 warning,不新增严格合同或手工交付入口;补针对性测试和 `unidesk-cicd` 说明,提交独立 PR 且不自行合并,完成任务后将详细报告写入[任务报告](./details/cli-output-progressive-disclosure/R10_Task_Report.md)。
@@ -0,0 +1,54 @@
# R6.1 阶段报告
## 结论
Artificer 易用派单入口已在两个独立分支完成实现、本地验证、提交和 PR 创建,尚未合并、发布或执行真实 R6.3 canary,因此 R6.1 保持进行中。
## 根因
- 旧派单把目标私有仓库误当成 runner primary resource bundle
- 初始 `workspaceRef` 携带了不合法的 repo/branch 字段,服务端以 `schema-invalid` 拒绝;
- 修正 schema 后,runner 又尝试直接拉取私有 `pikainc/selfmedia` bundle,并以 `repo-not-found` 失败。
- 旧流程把创建、派发、Target 工作区校验拆散,调用者需要手写 schema 或补第二条 dispatch 命令。
- runner primary workspace 与真正任务 Target 没有形成可验证的执行边界。
## 已实现设计
- UniDesk YAML 统一声明 `client.transport=target-trans`、marker、CLI 相对路径和 Target 上的 UniDesk 工作区。
- 外层命令经 `trans <Target>:<unideskWorkspace>` 重入 Target,再完成预检、创建、自动派发和观察。
- Artificer 任务继续继承默认 UniDesk bundle;目标源码不再作为 runner primary bundle。
- 目标源码的探测、Git、编辑、验证和运行面观察固定使用 `trans <Target>:<targetWorkspace>`
- `targetWorkspace` 必须是任务专属 clean worktreepreflight 校验 origin、branch、HEAD 与远端 ref commit。
- preflight 失败返回 `mutation=false` 且不创建 task;成功时披露合法 workspaceRef、bundle、session、SecretRef presence 和源码身份。
- live submit 后自动 dispatch;稳定 idempotency key 和并发回读防止重试重复派发。
- `unidesk-subagent` 固化原生子代理与 Artificer 均先登记 MDTODOprompt 与报告回链同一 MDTODO ID。
- AgentRun Artificer AipodSpec 强制 required skill `unidesk-trans`、runner tools bundle 和 `unidesk-ssh` SecretRef。
## 提交与 PR
- AgentRun
- 分支:`fix/artificer-source-override`
- 提交:`3d1d8a8a4a41d17053d8c1c0ad3d541272355846`
- PR[pikasTech/agentrun#349](https://github.com/pikasTech/agentrun/pull/349)。
- UniDesk
- 分支:`fix/artificer-target-dispatch-ux`
- 提交:`43124d01e8977f1b12878a852e223b5deb02a20b`
- PR[pikasTech/unidesk#1911](https://github.com/pikasTech/unidesk/pull/1911)。
## 验证
- UniDesk
- `bun test scripts/src/agentrun.test.ts`20 tests、397 assertions 通过;
- `bun scripts/cli.ts agentrun create --help`:单命令 Target 参数展示正确;
- `git diff --check`:通过。
- AgentRun
- `bun run src/selftest/run.ts 81-artificer-nonrecursive-prompt 90-runner-image-tools`11 assertions 通过;
- `git diff --check`:通过。
## 后续验收
- 先审查并合并 AgentRun PR,使 owning AipodSpec 进入受控运行面。
- 再审查并合并 UniDesk PR,使 Target 派单入口进入主线。
- 从最新 `feat/r6-ocr-visual-planner` 创建并推送 `/root/.worktrees/selfmedia/r6-ocr-visual-planner`
- 先用同一 R6.3 命令执行 dry-run,再执行 live;确认只创建一个 task/attempt 且自动生成 run/session。
- 从 runner 证据确认实际通过 `trans NC01:/root/.worktrees/selfmedia/r6-ocr-visual-planner` 完成读取、编辑、验证和 Git 操作。
@@ -0,0 +1,60 @@
# R6.2 只读诊断报告
## 结论
AgentRun NC01 v0.2 自动交付链已经自行收敛,不需要代码、YAML 或运行面修复。本轮没有创建分支、提交或 PR,也没有执行 mirror、PipelineRun、apply、refresh、sync、rollout、patch 等 mutation。
## 自动链证据
受控只读入口:
```bash
bun scripts/cli.ts platform-infra pipelines-as-code status \
--target NC01 \
--consumer agentrun-nc01-v02 \
--json
```
结果:
- `mutation=false``summary.ready=true`
- latest PipelineRun`agentrun-nc01-v02-ci-eafac6023ca5bd6afc8cf1f98788974524d21rjxkt`
- source commit`eafac6023ca5bd6afc8cf1f98788974524d219b2`
- PipelineRun`Succeeded`,耗时 54 秒;
- plan、collect、GitOps promote 三个 terminal role 均成功;
- artifact digest`sha256:963522c48906a290bba1901b5ae5b6b78c892b9f5ddd5d42cfb8885c3f1d7284`
- GitOps commit`8efa73c802b245275102fc7cc91a9f8b3013c5dc`
- Argo`Synced``Healthy`revision 与 GitOps commit 完全相同;
- runtime`agentrun-mgr` 为 1/1 ready,运行镜像 digest 与 artifact digest 完全相同;
- diagnostics`pac-ready-gitops-exact`phase 为 `ready`
- provenance relation`exact`
## Argo 与 GitOps authority 核对
- 当前 Argo `repoURL` 仍是 `http://git-mirror-http.devops-infra.svc.cluster.local:8080/pikasTech/agentrun.git`
- 当前状态投影中的 `expectedRepoURL``observedRepoURL` 相同;
- `revisionRelation.relation=exact``proof=owning-git-commit-graph``fetchOk=true`
- GitOps promote 已成功,Argo 已取得完全相同的 GitOps commit
- 因此本次没有观察到 Gitea GitOps 写入 authority 漂移,也没有因 legacy read endpoint 阻塞交付。
## lane-secret-missing 判定
补充只读入口:
```bash
bun scripts/cli.ts agentrun control-plane status \
--node NC01 \
--lane nc01-v02 \
--source-commit eafac6023ca5bd6afc8cf1f98788974524d219b2
```
该兼容摘要仍返回 `lane-secret-missing`,但同一结果同时显示:
- PipelineRun 成功;
- Argo `Synced/Healthy` 且 revision exact
- manager source 为 `eafac6023ca5`
- manager 1/1 ready
- runtime digest 与 artifact digest 一致;
- database Secret 存在。
因此 `lane-secret-missing` 不是本次 source delivery、GitOps、Argo 或 manager rollout 的真实 blocker,而是独立的 lane Secret presence 信号。自动交付是否完成以 PaC canonical 状态的 `ready=true``pac-ready-gitops-exact` 和 runtime exact provenance 为准,本轮不据此创建修复 PR。
@@ -0,0 +1,55 @@
# R6.4 任务报告
## 结论
Target 单命令 live 输出已在 PR #1916 中改为同源有界投影。默认 human、显式 JSON 与 YAML 不再嵌入完整 task、prompt、payload 或 Aipod 对象,同时保留派单身份、状态、决策和可直接执行的下钻命令。
任务保持处理中,等待主代理审查、合并与真实运行面复测后再完成。
## 根因
- `renderMutationSummary()` 对 JSON/YAML 直接序列化 auto-dispatch 原始结果;
- 原始结果包含完整 task、run、command、latestAttempt、prompt、payload 与 Aipod
- 外层 Target trans 重新渲染后超过 YAML-first `output.maxStdoutBytes=10240`
- stdout guard 因而用 `stdout-text-bytes-exceeded-threshold` 替换整个结果,导致 task/run/session identity 丢失。
## 实现
- 新增 `scripts/src/agentrun/target-task-output.ts`
- 只对 Target live 的 `dispatched``idempotent-replay``concurrent-replay` 生效;
- 输出 task、attempt、run、command、runner、session identity 与 state
- 输出 auto-dispatch decision、Target/MDTODO 摘要;
- 输出 Aipod 与 provider/tool SecretRef presence 摘要;
- 输出绑定本次 identity 并继承 Target/lane 的 `Next` 命令;
- 对字符串和 credential 列表限界,并在裁剪前移除仓库 URL userinfo
- `scripts/src/agentrun/render.ts` 让默认 human、JSON 与 YAML 共用紧凑事实;
- `--raw` 继续保留显式完整服务端对象语义;
- target-trans 与 auto-dispatch 的提交、幂等和恢复逻辑没有修改;
- CLI 参考已同步到 `.agents/skills/unidesk-code-queue/references/resources.md`
## 回归与证据
- 命令:`bun test scripts/src/agentrun.test.ts`
- 结果:21 pass、0 fail、445 assertions
- 新回归构造超过 10 KiB 的 prompt,并覆盖默认 human、`-o json``-o yaml`
- 代表性原始 JSON93,322 B
- 默认 human1,959 B
- JSON5,385 B
- YAML3,282 B
- 三种输出均保留六类 identity,不包含测试 prompt 或 URL credential
- `git diff --check` 通过。
## 交付
- 分支:`fix/artificer-compact-output`
- commit`49b45ed868eca3dae4b305b59d709a5c10cf388e`
- PRhttps://github.com/pikasTech/unidesk/pull/1916
- 未合并、未部署;
- 未触碰正在运行的 selfmedia task 或其业务 worktree。
## 后续验收
- 主代理按受控 PR 流程完成审查与合并;
- 自动交付后重新执行 Target live canary
- 验证外层结果不再出现 `stdout-text-bytes-exceeded-threshold`
- 验证真实 task、run、session identity 与 id-specific `Next` 可见。
@@ -0,0 +1,8 @@
# R7.1 任务报告
- Issue[AgentRun #348](https://github.com/pikasTech/agentrun/issues/348)。
- 原生子代理在独立 v0.2 worktree 完成最小修复,PR[AgentRun #350](https://github.com/pikasTech/agentrun/pull/350)。
- Commit`ea8da03216ee486faa3d302967b76ee1e4b9e7bb`
- 变更仅将 `config/aipods/artificer.yaml#spec.executionPolicy.timeoutMs``1800000` 调整为 `7200000`,并在既有 Aipod render 自测中断言 Queue task 继承该 YAML 值。
- 验证:`76-aipod-spec` 21 项通过,`bun --check``git diff --check` 通过。
- 未修改其他 Aipod、运行时代码默认、合同、租约、安全机制或围栏;未由 Artificer 递归修复。
@@ -0,0 +1,7 @@
# R7.2 任务报告
- 主代理完成 PR #350 的完整正文、两文件 diff、YAML authority 与测试断言审查。
- `gh pr preflight``MERGEABLE/CLEAN`,无 blocker。
- PR #350 已以 merge 方式合并,merge commit`eafac6023ca5bd6afc8cf1f98788974524d219b2`
- 合并后未执行 mirror、PipelineRun、GitOps、Argo、rollout 或其他人工 CI/CD 触发,只进入自动链只读观察。
- 已持久化旧 task/run 继续保留旧 `1800000` 快照;新任务在自动上线后读取 `7200000`
@@ -0,0 +1,12 @@
# R7.3 任务报告
Artificer 两小时 turn budget 已完成运行面长时 canary 验收。
- owning YAML`config/aipods/artificer.yaml#spec.executionPolicy.timeoutMs=7200000`
- 实时 `aipodspec/Artificer` 完整脱敏投影确认 `timeoutMs=7200000`,模型为 `gpt-5.6-sol`、推理强度为 `medium`
- 新任务:`qt_69f7eb35969e43f083988ce08b6af475`run`run_da550671d6584edf93e918f74277e6cb`session`sess_artificer_e4934c849b431ff942df8323`
- runner 启动时间:`2026-07-13T07:44:27.750Z`;在 `2026-07-13T08:14:43Z` 再次只读观察时仍为同一 run/session、Pod `Running`、command acknowledged,已持续约 30 分 15 秒且未出现旧 30 分钟 hard-timeout。
- 同次观察 supervisor 仍报告 `remainingMs=6980494``within-budget`,与两小时策略一致。
- 既有 Monitor run 保持创建时的旧 30 分钟 task 快照,未被回写或篡改;其自动 resume 行为独立验证正常。
发布全程只观察 GitHub merge 自动触发的 PaC/运行面事实,未人工 trigger、sync、refresh、PipelineRun 或 rollout。
@@ -0,0 +1,14 @@
# R7 任务报告
[AgentRun #348](https://github.com/pikasTech/agentrun/issues/348) 已完成:Artificer 单 turn hard-timeout 的唯一 owning fact 已由 Aipod YAML 从 30 分钟调整为 2 小时。
- PR[AgentRun #350](https://github.com/pikasTech/agentrun/pull/350)。
- merge commit`eafac6023ca5bd6afc8cf1f98788974524d219b2`
- YAML`config/aipods/artificer.yaml#spec.executionPolicy.timeoutMs=7200000`
- 修改仅包含 YAML 和一项既有 Aipod render 自测断言;未增加代码默认、第二 timeout authority、客户端续跑补丁、合同、租约、安全机制或围栏。
- 定向测试 21 项通过,语法检查与 `git diff --check` 通过。
- 自动 PaC PipelineRun 成功;实时 AipodSpec 已显示 7200000 ms。
- 新 Artificer task 在同一 run/session 下连续运行约 30 分 15 秒仍保持 `Running`supervisor 仍有约 6,980,494 ms 预算,确认旧 30 分钟 hard-timeout 已取消。
- 既有 Monitor task 的旧快照未被回写;其 session resume 继续沿原策略自动运行。
全程未人工触发 CI/CD、mirror、PipelineRun、Argo sync/refresh 或 rollout。
@@ -18,3 +18,7 @@
### R1.3
在独立 worktree 提交 PR,完成 Python/skill 校验和临时 fixture 前向验收;合并后更新本机受控 skill 安装并用本 MDTODO 的真实 Markdown 标题复测,完成任务后将详细报告写入[任务报告](./details/mdtodo-tooling-reliability/R1.3_Task_Report.md)。
## R2 [in_progress]
解决 UniDesk #1913https://github.com/pikasTech/unidesk/issues/1913):复用 agent_skills #7 与 PR #8 的 MDTODO 安全 stdin 实现,让主代理、Artificer resource bundle 和受控 host skill 投影默认使用安全标题输入,并有界披露 source/target freshness、warning 与精确修复入口;不得复制第二套 MDTODO 工具、增加版本门禁或覆盖宿主并行脏改,由 Artificer 在独立 worktree 和分支实现、测试并提交 PR,不自行合并,完成任务后将详细报告写入[任务报告](./details/mdtodo-tooling-reliability/R2_Task_Report.md)。
@@ -64,6 +64,9 @@
记录并后续解决 [UniDesk #1828](https://github.com/pikasTech/unidesk/issues/1828):聚合 cicd status 对 sentinel/platform-infra 在 source、GitOps、Argo/runtime 已对齐时仍误报 pac-gitops-missing/relation unknown;统一专用 control-plane 与聚合器的 source→GitOps revision relation authority 和判定证据,本轮仅登记,不扩入 #1760 自动 delivery 修复,完成任务后将详细报告写入[任务报告](./details/pr-merge-driven-automatic-delivery/R4.4_Task_Report.md)。
### R4.5 [in_progress]
基于 [UniDesk #1802](https://github.com/pikasTech/unidesk/issues/1802) 修复当前 AgentRun NC01 自动交付阻塞:将 GitOps read/write、Argo repoURL 与 delivery provenance 从 legacy git-mirror 收敛到既有 Gitea internal 单一 authority;只做 owning YAML、既有 renderer/定向验证的最小必要修改,禁止人工 mirror、sync、flush、trigger、apply、closeout、PipelineRun 或 runtime patch,也禁止新增合同、租约、安全机制和围栏,完成任务后将详细报告写入[任务报告](./details/pr-merge-driven-automatic-delivery/R4.5_Task_Report.md)。
## R5 [in_progress]
解决 [UniDesk #1900](https://github.com/pikasTech/unidesk/issues/1900):修复 NC01 PaC watcher 因 ServiceAccount token 的 node/pod 关系失败而 Pending,恢复 GitHub PR merge 驱动的唯一自动交付链,禁止人工补触发、第二权威或旁路,完成任务后将详细报告写入[任务报告](./details/pr-merge-driven-automatic-delivery/R5_Task_Report.md)。
@@ -24,6 +24,9 @@ PR 合并后只等待自动发布链,使用受控 Web sentinel 原入口产生
核对 [#1861](https://github.com/pikasTech/unidesk/issues/1861) 的小时级实际探测 cadence 与 10 分钟 scheduler heartbeat/扫描间隔;若只读报告语义误导则纠正字段和文档,若 owning YAML 未生效则修正渲染,验收 CronJob 与报告明确区分二者,完成任务后将详细报告写入[任务报告](./details/web-sentinel-runtime-reliability/R1.4_Task_Report.md)。
#### R1.4.1 [in_progress]
解决 [UniDesk #1919](https://github.com/pikasTech/unidesk/issues/1919):纠正此前‘实际已为小时级’的误判,沿 owning YAML、renderer、CronJob/Deployment、quick verify 与 AgentRun/HWLAB Session provenance 定位并消除约 10 分钟误派发;保留 10 分钟心跳/扫描时不得创建业务 Session,自动交付后覆盖完整小时窗口验收唯一小时级派发源,不新增第二 scheduler、补洞、合同、租约、安全机制或围栏,完成任务后将详细报告写入[任务报告](./details/web-sentinel-runtime-reliability/R1.4.1_Task_Report.md)。
## R2 [in_progress]
推进 [UniDesk #1868](https://github.com/pikasTech/unidesk/issues/1868):按当前多 runner、全局查询与 Dashboard 复杂度重构 Monitor 平台,评估并实施分散 SQLite 到 NC01 YAML-first Host PostgreSQL 的统一迁移;P0 先冻结架构、数据模型和切换边界,再实现、自动发布和原入口验收,不以迁移掩盖 [#1861](https://github.com/pikasTech/unidesk/issues/1861),不保留永久双写或第二权威,完成任务后将详细报告写入[任务报告](./details/web-sentinel-runtime-reliability/R2_Task_Report.md)。
@@ -27,6 +27,7 @@
| Monitor 中心持久化实现引用版本 | draft-2026-07-12-p17-monitor-central-persistence |
| Cadence/OTel 稳定性实现引用版本 | draft-2026-07-01-p15-cadence-otel |
| CI/CD source snapshot 实现引用版本 | draft-2026-07-01-p16-cicd-source-snapshot |
| 浏览器资源治理实现引用版本 | draft-2026-07-13-p18-browser-resource-guard |
| 需求规格模板 | [ISO/IEC/IEEE 29148 需求规格模板](../../templates/iso-iec-ieee-29148-requirements-spec-template.md) |
| 上级规格 | [PJ2026-010605 运维监控](PJ2026-010605-observability-monitoring.md) |
| 关联规格 | [PJ2026-010401 Web工作台](PJ2026-010401-web-workbench.md)、[PJ2026-0104010803 Workbench唯一投影](PJ2026-0104010803-workbench-unique-projection.md)、[PJ2026-010403 API契约](PJ2026-010403-api-contract.md)、[PJ2026-010601 发布流水](PJ2026-010601-controlled-release.md)、[PJ2026-010602 源码同步](PJ2026-010602-source-sync.md)、[PJ2026-010603 YAML运维](PJ2026-010603-yaml-first-ops.md)、[PJ2026-010604 公开入口](PJ2026-010604-public-entry.md)、[PJ2026-01060505 Workbench性能](PJ2026-01060505-workbench-performance.md) |
@@ -133,6 +134,7 @@ Web哨兵必须遵循 UniDesk YAML-first ops。目标 node/lane、public origin
| PJ2026-0106050812 | Monitor Web 观察面板治理 | 本规格 6.12 | 趋势曲线、运行时间线、固定视口三栏、cadence freshness、Vue CI/CD/env reuse/git mirror | Monitor 中心服务与 Web、Dashboard工作台、发布集成、源码同步 | 可滚动上线和值守的统一观察面板 |
| PJ2026-0106050814 | 哨兵 CI/CD 可见性 | 本规格 6.14 | publish 阶段耗时、env reuse、docker cache、超时诊断、git mirror/Argo/runtime 收敛下一步 | 发布集成、源码同步、Monitor Web 观察面板治理 | 小改动滚动上线可诊断、可续跑、可验收 |
| PJ2026-0106050815 | Cadence/OTel 稳定性 | 本规格 6.15 | Kubernetes CronJob 周期巡检、状态缺口故障码、monitor-web cadence 可见性和 sentinel OTel span 合同 | Monitor Web 观察面板治理、发布集成、OTel、YAML运维 | JD01/v03 周期巡检恢复和后续防回归 |
| PJ2026-0106050818 | 浏览器资源治理 | 本规格 6.18 | 物理内存启动资格、短窗口最终复核、浏览器有界证据与精确收尾 | Wrapper边界、YAML配置、平台运维 | WebProbe 人工入口与 cadence 稳定运行 |
### 5.1 目标架构图
@@ -755,6 +757,34 @@ Web 哨兵必须向平台 OTel 后端发出有界、脱敏的 span 或在状态
CI/CD rollout 门禁仍只验证配置声明的 `/health` endpoint。web-probe quick verify、Playwright/browser render、dashboard screenshot 和 OTel trace search 都是独立的 post-deploy evidence;不得重新塞回 `trigger-current --confirm --wait` 的同步门禁,也不得引入 Docker daemon/socket 依赖。
### 6.18 OPS-SENTINEL-REQ-018 浏览器资源治理
| 编号 | 短名 | 主责模块 | 关联模块 |
| --- | --- | --- | --- |
| OPS-SENTINEL-REQ-018 | 浏览器资源治理 | PJ2026-0106050818 浏览器资源治理 | Wrapper边界、YAML配置、平台运维 |
- 启动资格:
- 所有会创建 Playwright/Chrome 的 WebProbe 人工入口与 sentinel cadence 必须在真正启动浏览器前读取目标主机 `/proc/meminfo``MemAvailable`
- 启动阈值、比较语义、最终复核等待和浏览器证据上限只由 owning YAML 提供;
- swap 作为独立压力信号展示,不得计入启动资格。
- 当前 HWLAB v0.3 策略:
- 阈值为 `4 GiB`
- 人工入口在 `MemAvailable <= 4 GiB` 时阻断,并提示通过受控 GC 完成 plan、run、终态查询和重新读取;
- sentinel cadence 在 `MemAvailable < 4 GiB` 时跳过本轮并等待下一 cadence,不创建 observer 或 Chrome,也不自动执行 GC。
- 并发启动保护:
- 只允许使用覆盖“最终重新读取 `MemAvailable` 到确认真实 Chrome/Chromium executable 已就绪”这一实际风险窗口的最小锁;
- 确认就绪后必须立即释放;
- 不得扩展为业务任务租约、第二生命周期 authority、证明链、通用状态机或新的服务合同。
- 终态收尾:
- 成功、失败、timeout 和 signal 路径都必须关闭 context、browser 和本次启动的精确 process group
- 进程归属只以 `/proc` 中的实际 executable、PID/PGID 与精确后代关系判定;
- 不接受 Playwright driver 名称或结构化输出自报成功;
- 禁止 `pkill``killall` 和按名称批量终止。
- 证据容量:
- 截图默认使用 owning YAML 声明的 viewport 模式,只有显式人工参数可以请求 full-page;
- network、failure、response body、性能、截图、样本和历史证据必须有 YAML 上限;
- 不得用无限数组、无限响应体或 full-page 默认值放大 WebProbe 自身内存。
## 7. 过程控制
Web哨兵架构执行 issue 为 [#883](https://github.com/pikasTech/unidesk/issues/883)。阶段跟踪 issue 为 P0 [#885](https://github.com/pikasTech/unidesk/issues/885)、P1 [#886](https://github.com/pikasTech/unidesk/issues/886)、P2 [#887](https://github.com/pikasTech/unidesk/issues/887)、P3 [#888](https://github.com/pikasTech/unidesk/issues/888)、P4 [#889](https://github.com/pikasTech/unidesk/issues/889)、P5 [#890](https://github.com/pikasTech/unidesk/issues/890) 和 P6 [#891](https://github.com/pikasTech/unidesk/issues/891)。
@@ -784,3 +814,10 @@ P14 Web 哨兵 CI/CD 可见性执行 issue 为 [#1285](https://github.com/pikasT
P15 Cadence 调度稳定性与 OTel 覆盖执行 issue 为 [#1372](https://github.com/pikasTech/unidesk/issues/1372)。P15 closeout 必须回写:SPEC P15 引用、[#1374](https://github.com/pikasTech/unidesk/issues/1374)-[#1378](https://github.com/pikasTech/unidesk/issues/1378) 阶段状态、JD01/v03 `jd01-web-probe-sentinel` CronJob manifest/GitOps/Argo/runtime observed 证据、`sentinel-cadence-cronjob-missing` 防回归状态、monitor-web cadence/OTel coverage 显示、OTel trace search 或 instrumentation-gap 证据、受控 rollout/publish job、GitOps revision、source commit、dashboard/health 验收,以及 CI/CD 门禁仍只验证 `/health` 的证据。
P17 Monitor 中心持久化与架构重构执行 issue 为 [#1868](https://github.com/pikasTech/unidesk/issues/1868)P0 调研为 [#1869](https://github.com/pikasTech/unidesk/issues/1869)、[#1870](https://github.com/pikasTech/unidesk/issues/1870) 和 [#1871](https://github.com/pikasTech/unidesk/issues/1871)。P17 必须按“Host PG 前置健康、中心 store/ingest/query、runner terminal submit、一次性迁移与原子切换、monitor-web/CLI 中心读取、自动发布与原入口验收”顺序推进。closeout 必须记录 SQLite 冻结 fingerprint/行数、PG 导入校验、global latest 规则、artifact locator 可达性、runner 停机后历史可读、旧 SQLite 与 runner dashboard 已退出读写路径,以及 PR 合并后的自动 CI/CD 证据。
P18 浏览器资源治理执行 issue 为 [#1907](https://github.com/pikasTech/unidesk/issues/1907)。P18 closeout 必须回写:
- owning YAML 配置引用和所有浏览器创建入口矩阵;
- `MemAvailable` 与 swap 分离、人工阻断和 cadence 跳过边界;
- 精确 context/browser/process-group 收尾和证据容量上限;
- 无真实浏览器的边界测试和 NC01 只读资源复核。
@@ -2,11 +2,9 @@
## 修改历史
| 版本 | 对应 commit id | 更新日期 | 变更说明 |
| --- | --- | --- | --- |
| v0.1 | 待提交 | 2026-07-13 | 基于 `/root/selfmedia` 已跑通能力建立智媒工厂 L0 总规格和七个 L1 能力域。 |
当前正文仍在规格治理草稿中;未定稿前不新增版本号,不为单次编辑追加 `待提交` 版本。
| 版本 | 更新日期 | 变更说明 |
| --- | --- | --- |
| v0.1 | 2026-07-13 | 定义智媒工厂预期终态和七个 L1 能力域。 |
## 正文
@@ -19,12 +17,16 @@
| 编号 | PJ2026-02 |
| 短名 | 智媒工厂 |
| 层级 | L0 总项目 |
| 状态 | 已生效 |
| 实现引用版本 | draft-2026-07-13-p0 |
| 规格状态 | 已生效 |
| 需求规格模板 | [ISO/IEC/IEEE 29148 需求规格模板](../../templates/iso-iec-ieee-29148-requirements-spec-template.md) |
| 规格治理索引 | 本文第 7 章 |
本文采用 ISO/IEC/IEEE 29148 需求规格模板的项目裁剪版。正文只保留稳定使命、范围、术语、系统边界、能力域分工、原子需求和必要过程控制。
本文采用 ISO/IEC/IEEE 29148 需求规格模板的项目裁剪版。
正文边界如下:
- 只定义预期终态、目标能力、稳定边界和验收契约;
- 不记录实现进度或阶段基线。
## 2. 目的和范围
@@ -34,49 +36,56 @@
- 面向科技资讯创作者和技术团队;
- 把资讯发现、事实核验、编辑策划、动态图文、配音、字幕、成片和发布材料收敛为本地优先的生产链;
- 当前以嵌入式与 AI 交叉、纯嵌入式和 AI 为首要内容方向;
- 长期成为可配置、可单步调试的技术内容生产基础设施;
- 以嵌入式与 AI 交叉、纯嵌入式和 AI 为首要内容方向;
- 成为可配置、可单步调试的技术内容生产基础设施;
- 使每一期节目都能从来源证据追溯到口播、画面、成片和评论区引用。
### 2.2 当前能力基线
### 2.2 预期终态
- 资讯收集
- 通过 YAML 登记 RSS、Atom、Hacker News API 和人工来源;
智媒工厂终态应同时具备
- 可追溯资讯底座:
- 通过 owning YAML 管理 RSS、Atom、公开 API、Hacker News 和人工来源;
- 保留一手来源、发现来源、信任层级、分类、版权提示和原始响应;
- 优先处理嵌入式与 AI、纯嵌入式、AI,并识别优惠、样片和免费额度等实用信息。
- 编辑策划:
- 支持授权 LLM 和完全离线模板两种文稿路径;
- 生成固定开场卡片大纲、条口播、术语补充、总结、参考来源和评论区文案;
- 优先处理嵌入式与 AI、纯嵌入式、AI 和实用优惠信息。
- 有效编辑策划:
- 提供授权 LLM 和完全离线模板两种文稿路径;
- 生成合并开场卡片大纲、条口播、专业对象补充、参考来源和评论区文案;
- 最后一条资讯结束后直接进入参考来源,不生成总结场景;
- 对状态、日期、数字、媒体归因、预印本和专业对象说明实施质量门。
- 视觉制作
- 采集来源网页GitHub 页面为本地视觉工件;
- 为生成内容输出暖色、圆角、可配置的 HTML 动效、PNG 预览和 PPTX
- 以确定性时间驱动页面滚动、章节进度和阶段导航。
- 音频成片:
- 使用本地开源中文 TTS 生成可缓存配音;
- 由 YAML 控制音色、语速、停顿、响度、字幕和编码
- 可审查动态视觉:
- 采集实际来源网页GitHub 页面或论文页面为本地视觉工件;
- 使用本地 OCR 和可审查的视觉规划选择信息充分的画面
- 生成以白色和绿色为主、暖阳圆角、无渐变的 HTML 动效、PNG 预览和 PPTX
- 以确定性时间驱动页面滚动、重点放大、章节进度和阶段导航。
- 自然音频成片:
- 使用本地开源中文 TTS 生成可缓存、可替换音色和可调语速的配音
- 由 owning YAML 管理音色、语速、停顿、响度、字幕和编码;
- 生成 WAV、时间线、SRT、ASS、MP4、封面和自动检查报告。
- 流水线平台:
- 可调试流水线平台:
- TypeScript CLI 支持单步、区间和完整流水线;
- 长任务采用异步作业,提供状态、进度、日志、结果、重试和取消;
- 同源 Web 编辑台展示期次、作业、日志、费用和已登记媒体工件;
- 当前工件清单是步骤输出索引,不是经过内容版本兼容性校验的完整发布包。
- 发布运营:
- 同源 Web 编辑台展示期次、作业、日志、费用和全部已登记工件;
- 工件按内容版本兼容性组合为完整发布包。
- 受控发布运营:
- 生成与最终成片真实章节起点一致的评论区时间轴;
- 输出参考来源、公开目录价成本估算、技术验收结果和人工复核提示
- 输出参考来源、公开目录价成本估算、技术验收结果和人工复核材料
- 持久化批准人、批准时间、退回记录、发布版本和反馈台账;
- 通过本地或公网 IP 提供候选成片和工件的受控预览。
- 隔离自动交付:
- 提供 Web 内置 Codex 终端、版本化后端容器、会话 PVC 和重启自动恢复;
- 通过 YAML-first Kubernetes 投影相互隔离的 Secret,并从公网 IPv4 原入口验收;
- 建立 GitHub、mirror、PaC、Tekton、GitOps 和 Argo 自动交付链;
- Web 工厂与生成内容主题解耦,采用工业风和紧凑高信息密度布局。
### 2.3 当前差距与目标边界
### 2.3 终态验收边界
- 当前没有持久化的人工批准状态、批准人、批准时间、退回记录或发布版本台账
- 当前没有把全部媒体、来源、费用和检查工件按内容版本兼容性组合成发布包
- 当前没有 Web 内置 Codex 终端、版本化后端容器、会话 PVC 或重启自动恢复
- 当前没有 YAML-first Kubernetes 部署、隔离 Secret 投影或公网 IP 部署后验收
- 当前没有 GitHub、mirror、PaC、Tekton、GitOps 和 Argo 自动交付链。
- 当前 Web 编辑台仍需与生成内容主题解耦,目标控制台采用工业风和紧凑高信息密度布局。
- 第 6 章使用“应”描述目标需求;目标要求不代表当前实现或当前上线状态。
- 七个 L1 必须分别满足原入口验收,才能宣称 L0 终态达成
- 单机样片、一次成功作业、临时公网入口或部分步骤工件不能替代 L0 终态验收
- 自动技术检查通过不能替代人工事实、版权、听感、平台规则和发布批准
- 执行态、实现差距和一次性证据只进入阶段报告、任务报告、偏离记录或执行 issue
### 2.4 长期愿景
### 2.4 扩展目标
- 形成多栏目、多主题、多语言和多平台复用的内容生产模型,同时保持每个栏目独立的来源、策划、视觉、声音和发布策略。
- 让人工编辑能够在任何阶段插入、覆盖、重跑或批准结果,不被端到端自动化锁死。
@@ -115,14 +124,14 @@
| 一手来源 | 由产品、项目、公司、机构或作者团队直接发布的公告、发布说明、论文或仓库记录。 |
| 发现来源 | 用于发现线索和提供编辑背景的媒体、社区或聚合入口,不替代可获得的一手事实。 |
| 来源闭环 | 口播中的可核验事实能够解析到期次来源项,并在引用与发布材料中保持同一归因。 |
| 内容单元 | 开场大纲、单条资讯、总结或参考来源等可独立配音映射场景的结构化文稿单元。 |
| 内容单元 | 开场大纲、单条资讯或参考来源等可独立配音映射场景的结构化文稿单元。 |
| 场景 | 具有独立画面、时间范围、章节标题和动效状态的视频组成单元。 |
| 工件 | 流水线生成并登记的来源、文稿、视觉、音频、字幕、视频、报告或发布文本文件。 |
| 发布候选 | 已通过自动技术检查但仍需人工事实、版权、听感和平台规则复核的成片及配套材料。 |
| 发布包 | 目标能力中与同一期次、内容版本和工件哈希绑定的候选媒体、引用、费用、检查和批准材料集合。 |
| 平台交付 | 将应用、内置 Codex、隔离会话和凭据通过受控 Kubernetes 与自动链交付到公网原入口的能力域。 |
| owning YAML | 对可调业务事实具有唯一所有权的 YAML 配置;代码只负责校验、调度和渲染。 |
| 实现引用版本 | 后续源码变更用于声明其遵循哪一版规格语义的稳定标识。 |
| 规格版本 | 用于区分规格语义变更的稳定标识。 |
## 4. 系统边界和接口
@@ -142,15 +151,25 @@
L1 按完成标准划分为七个稳定能力域。仓库、框架、模型、命令和运行端口只是实现引用,不构成 L1。
| 编号 | 内部模块 | 规格文档 | 主责边界 | 上游依赖 | 下游支撑 |
| --- | --- | --- | --- | --- | --- |
| PJ2026-0201 | 资讯源 | [PJ2026-0201 资讯源](PJ2026-0201-information-sources.md) | 来源发现、采集、证据、归一化、分类、排序和版权边界 | 公开来源、编辑者配置 | [编辑策划](PJ2026-0202-editorial-planning.md)、[视觉制作](PJ2026-0203-visual-production.md)、[发布运营](PJ2026-0206-publishing-operations.md) |
| PJ2026-0202 | 编辑策划 | [PJ2026-0202 编辑策划](PJ2026-0202-editorial-planning.md) | 选题、核验、叙事、口播、来源引用和栏目结构 | [资讯源](PJ2026-0201-information-sources.md) | [视觉制作](PJ2026-0203-visual-production.md)、[音频成片](PJ2026-0204-audio-video-production.md)、[发布运营](PJ2026-0206-publishing-operations.md) |
| PJ2026-0203 | 视觉制作 | [PJ2026-0203 视觉制作](PJ2026-0203-visual-production.md) | 来源视觉、场景设计、HTML 动效、演示文稿、进度导航和封面画面 | [资讯源](PJ2026-0201-information-sources.md)、[编辑策划](PJ2026-0202-editorial-planning.md) | [音频成片](PJ2026-0204-audio-video-production.md)、[发布运营](PJ2026-0206-publishing-operations.md) |
| PJ2026-0204 | 音频成片 | [PJ2026-0204 音频成片](PJ2026-0204-audio-video-production.md) | 本地配音、时间线、字幕、音视频合成和媒体质量 | [编辑策划](PJ2026-0202-editorial-planning.md)、[视觉制作](PJ2026-0203-visual-production.md) | [发布运营](PJ2026-0206-publishing-operations.md) |
| PJ2026-0205 | 流水线平台 | [PJ2026-0205 流水线平台](PJ2026-0205-pipeline-platform.md) | YAML 配置、CLI 调度、异步作业、可观测性、工件契约、用量计费和 Web 控制面 | 全部生产能力域的步骤契约 | 全部 L1 的可执行、可调试和可观察运行 |
| PJ2026-0206 | 发布运营 | [PJ2026-0206 发布运营](PJ2026-0206-publishing-operations.md) | 发布候选、引用与时间轴、人工复核、版本归档、预览和反馈回流 | [资讯源](PJ2026-0201-information-sources.md)、[编辑策划](PJ2026-0202-editorial-planning.md)、[视觉制作](PJ2026-0203-visual-production.md)、[音频成片](PJ2026-0204-audio-video-production.md)、[流水线平台](PJ2026-0205-pipeline-platform.md) | 内容发布者和后续栏目改进 |
| PJ2026-0207 | 平台交付 | [PJ2026-0207 平台交付](PJ2026-0207-platform-delivery.md) | Web Codex、隔离会话与凭据、K8s、公网 IP、自动交付和运行验收 | [流水线平台](PJ2026-0205-pipeline-platform.md)、[发布运营](PJ2026-0206-publishing-operations.md) | 受控运行面、编辑者和交付自动化 |
| 编号 | 内部模块 | 规格文档 | 主责边界 |
| --- | --- | --- | --- |
| PJ2026-0201 | 资讯源 | [资讯源规格](PJ2026-0201-information-sources.md) | 来源、证据、分类和版权边界 |
| PJ2026-0202 | 编辑策划 | [编辑策划规格](PJ2026-0202-editorial-planning.md) | 选题、核验、叙事和引用 |
| PJ2026-0203 | 视觉制作 | [视觉制作规格](PJ2026-0203-visual-production.md) | 来源视觉、场景、动效和封面 |
| PJ2026-0204 | 音频成片 | [音频成片规格](PJ2026-0204-audio-video-production.md) | 配音、字幕、时间线和成片 |
| PJ2026-0205 | 流水线平台 | [流水线平台规格](PJ2026-0205-pipeline-platform.md) | 配置、调度、作业、工件和控制面 |
| PJ2026-0206 | 发布运营 | [发布运营规格](PJ2026-0206-publishing-operations.md) | 发布候选、复核、归档和反馈 |
| PJ2026-0207 | 平台交付 | [平台交付规格](PJ2026-0207-platform-delivery.md) | 隔离运行、自动交付和运行验收 |
上下游关系如下:
- [资讯源](PJ2026-0201-information-sources.md)接收公开来源和编辑者配置,支撑编辑策划、视觉制作和发布运营。
- [编辑策划](PJ2026-0202-editorial-planning.md)依赖资讯源,支撑视觉制作、音频成片和发布运营。
- [视觉制作](PJ2026-0203-visual-production.md)依赖资讯源和编辑策划,支撑音频成片和发布运营。
- [音频成片](PJ2026-0204-audio-video-production.md)依赖编辑策划和视觉制作,支撑发布运营。
- [流水线平台](PJ2026-0205-pipeline-platform.md)承载全部生产能力域的步骤契约和可观察运行。
- [发布运营](PJ2026-0206-publishing-operations.md)汇总全部内容生产结果,面向发布者和后续栏目改进。
- [平台交付](PJ2026-0207-platform-delivery.md)承载流水线平台与发布运营的受控运行面和自动交付。
### 5.1 目标架构图
@@ -243,9 +262,14 @@ flowchart TD
Runtime --> PublicEntry
```
数据流中的每个正式步骤必须读取已登记上游工件并输出新工件。上游缺失或不合法时应明确失败,不得使用隐藏默认值、旧期次文件或伪造输入补成成功。
数据流中的每个正式步骤必须
图中的自动交付、目标发布包和持久化人工复核属于目标能力。当前只具备主机运行、步骤工件、评论区材料和技术检查。
- 读取已登记上游工件;
- 输出新工件;
- 在上游缺失或不合法时明确失败;
- 不得使用隐藏默认值、旧期次文件或伪造输入补成成功。
图中的自动交付、发布包和持久化人工复核都属于终态必需能力。
### 5.3 关键时序图
@@ -281,31 +305,22 @@ sequenceDiagram
R-->>E: 发布候选或明确阻塞项
```
长任务必须异步执行。CLI 和 Web 提交后立即获得作业标识,编辑者通过状态、日志和工件观察进展;重试创建新作业并保留旧证据,不能覆盖失败历史。
长任务必须异步执行
图中的自动交付、内置终端和批准记录属于目标时序;当前实现从创建期次到技术检查和候选预览的部分已经跑通。
- CLI 和 Web 提交后立即获得作业标识;
- 编辑者通过状态、日志和工件观察进展;
- 重试创建新作业并保留旧证据;
- 重试不能覆盖失败历史。
### 5.4 当前实现映射
| 能力 | 当前实现引用 | 当前事实 |
| --- | --- | --- |
| 配置入口 | `/root/selfmedia/config/selfmedia.yaml` 及其 `configRefs` | YAML 是可调事实来源,配置缺失或不合法时失败。 |
| CLI 与作业 | `/root/selfmedia/scripts/selfmedia-cli.ts``scripts/src/jobs.ts``scripts/src/runner.ts` | 支持单步、完整流程、状态、日志、结果、重试和取消。 |
| 内容步骤 | `/root/selfmedia/scripts/src/steps/` | 已实现采集、文稿、视觉、幻灯、配音、字幕、渲染和检查。 |
| Web 编辑台 | `/root/selfmedia/scripts/src/server-app.ts``web/src/` | 同源展示期次、作业、用量和媒体工件。 |
| 期次事实 | `/root/selfmedia/data/editions/<edition-id>/` | 来源、文稿、媒体、时间线、用量和检查结果按期次归档。 |
| 已验证样片 | `embedded-ai-weekly-2026-07-13-r3` | 已形成九场景、五条资讯、网页滚动、配音字幕、成片、评论区和十项技术检查。 |
| 发布状态 | 当前无持久化批准与发布台账 | 已生成候选和复核材料,但没有批准人、批准时间、退回或已发布状态。 |
| 发布包 | 当前无版本兼容性发布包 | 存在步骤工件和输出清单,但尚未按内容版本校验并组合全部发布材料。 |
| 平台交付 | 当前无正式 Kubernetes 运行面 | Web Codex、隔离会话、YAML-first K8s 和自动交付均为 [PJ2026-0207 平台交付](PJ2026-0207-platform-delivery.md) 目标。 |
自动交付、内置终端和批准记录都属于终态关键时序,不得以部分时序成功宣称整期或整个平台达成。
## 6. 全局原子需求
### 6.1 SMF-L0-REQ-001 可信资讯底座
| 编号 | 短名 | 主责模块 | 关联模块 |
| --- | --- | --- | --- |
| SMF-L0-REQ-001 | 可信资讯底座 | [PJ2026-0201 资讯源](PJ2026-0201-information-sources.md) | [编辑策划](PJ2026-0202-editorial-planning.md)、[视觉制作](PJ2026-0203-visual-production.md)、[发布运营](PJ2026-0206-publishing-operations.md) |
| 编号 | 短名 | 主责模块 |
| --- | --- | --- |
| SMF-L0-REQ-001 | 可信资讯底座 | [资讯源](PJ2026-0201-information-sources.md) |
智媒工厂应建立可追溯的科技资讯底座,使每条进入节目候选池的事实都有来源身份、时间、角色、信任层级、分类、证据摘要和原始链接。
@@ -321,21 +336,40 @@ sequenceDiagram
### 6.2 SMF-L0-REQ-002 有效编辑叙事
| 编号 | 短名 | 主责模块 | 关联模块 |
| --- | --- | --- | --- |
| SMF-L0-REQ-002 | 有效编辑叙事 | [PJ2026-0202 编辑策划](PJ2026-0202-editorial-planning.md) | [资讯源](PJ2026-0201-information-sources.md)、[视觉制作](PJ2026-0203-visual-production.md)、[音频成片](PJ2026-0204-audio-video-production.md)、[发布运营](PJ2026-0206-publishing-operations.md) |
| 编号 | 短名 | 主责模块 |
| --- | --- | --- |
| SMF-L0-REQ-002 | 有效编辑叙事 | [编辑策划](PJ2026-0202-editorial-planning.md) |
智媒工厂应把来源证据转化为信息密度高、口语自然、对技术受众有实际价值的栏目文稿,并保持开场、概述、故事、总结和参考来源的稳定结构。
智媒工厂应把来源证据转化为栏目文稿,并满足:
编辑策划负责选题、事实边界、术语补充、媒体归因、状态限定和叙事节奏。下游模块只能消费结构化内容,不能以画面或配音修饰掩盖证据不足和文稿空泛。
- 信息密度高;
- 口语自然;
- 对技术受众有实际价值;
- 保持开场与大纲、逐条故事和参考来源的稳定结构。
完成标准是:每条资讯说清“发生了什么、谁发布、对受众有什么影响、目前不能据此推出什么”,并能通过来源引用复核关键事实。
编辑策划负责:
- 选题;
- 事实边界;
- 术语补充;
- 媒体归因;
- 状态限定;
- 叙事节奏。
下游模块只能消费结构化内容,不能以画面或配音修饰掩盖证据不足和文稿空泛。
完成标准如下:
- 每条资讯说清“发生了什么、由谁发布、对象有什么功能、对受众有什么价值”;
- 关键事实能够通过来源引用复核;
- 事实边界在相关表述处自然说明;
- 不使用机械的局限、约束或验证建议收尾。
### 6.3 SMF-L0-REQ-003 动态视觉表达
| 编号 | 短名 | 主责模块 | 关联模块 |
| --- | --- | --- | --- |
| SMF-L0-REQ-003 | 动态视觉表达 | [PJ2026-0203 视觉制作](PJ2026-0203-visual-production.md) | [资讯源](PJ2026-0201-information-sources.md)、[编辑策划](PJ2026-0202-editorial-planning.md)、[音频成片](PJ2026-0204-audio-video-production.md) |
| 编号 | 短名 | 主责模块 |
| --- | --- | --- |
| SMF-L0-REQ-003 | 动态视觉表达 | [视觉制作](PJ2026-0203-visual-production.md) |
智媒工厂应提供可配置、可追溯、适合科技资讯的动态视觉表达,使信息层级、来源画面、章节状态和叙事节奏在视频中一致呈现。
@@ -352,9 +386,9 @@ sequenceDiagram
### 6.4 SMF-L0-REQ-004 自然音频成片
| 编号 | 短名 | 主责模块 | 关联模块 |
| --- | --- | --- | --- |
| SMF-L0-REQ-004 | 自然音频成片 | [PJ2026-0204 音频成片](PJ2026-0204-audio-video-production.md) | [编辑策划](PJ2026-0202-editorial-planning.md)、[视觉制作](PJ2026-0203-visual-production.md)、[发布运营](PJ2026-0206-publishing-operations.md) |
| 编号 | 短名 | 主责模块 |
| --- | --- | --- |
| SMF-L0-REQ-004 | 自然音频成片 | [音频成片](PJ2026-0204-audio-video-production.md) |
智媒工厂应使用本地开源媒体能力把结构化文稿和场景合成为自然、同步、可重新配置的配音字幕成片。
@@ -364,13 +398,17 @@ sequenceDiagram
- 字幕切分、场景时间线、编码和媒体检查;
- 在改变语速或声音参数时只重跑受影响步骤,不要求重新采集和写稿。
完成标准是:音频、字幕、场景和视频时间线在允许误差内一致,媒体流、分辨率、响度、峰值和可解码性通过技术检查,人工仍能识别并退回读音或表达问题。
完成标准如下:
- 音频、字幕、场景和视频时间线在允许误差内一致;
- 媒体流、分辨率、响度、峰值和可解码性通过技术检查;
- 人工仍能识别并退回读音或表达问题。
### 6.5 SMF-L0-REQ-005 可调试生产平台
| 编号 | 短名 | 主责模块 | 关联模块 |
| --- | --- | --- | --- |
| SMF-L0-REQ-005 | 可调试生产平台 | [PJ2026-0205 流水线平台](PJ2026-0205-pipeline-platform.md) | [资讯源](PJ2026-0201-information-sources.md)、[编辑策划](PJ2026-0202-editorial-planning.md)、[视觉制作](PJ2026-0203-visual-production.md)、[音频成片](PJ2026-0204-audio-video-production.md)、[发布运营](PJ2026-0206-publishing-operations.md) |
| 编号 | 短名 | 主责模块 |
| --- | --- | --- |
| SMF-L0-REQ-005 | 可调试生产平台 | [流水线平台](PJ2026-0205-pipeline-platform.md) |
智媒工厂应提供 YAML-first、CLI-first 的生产平台,使每个正式步骤可独立运行、观察、重试和复现,并由同一实现组合为完整流水线和 Web 可视流程。
@@ -387,9 +425,9 @@ Web 工厂和控制台应采用工业风、紧凑高信息密度、直角或极
### 6.6 SMF-L0-REQ-006 受控发布闭环
| 编号 | 短名 | 主责模块 | 关联模块 |
| --- | --- | --- | --- |
| SMF-L0-REQ-006 | 受控发布闭环 | [PJ2026-0206 发布运营](PJ2026-0206-publishing-operations.md) | [资讯源](PJ2026-0201-information-sources.md)、[编辑策划](PJ2026-0202-editorial-planning.md)、[视觉制作](PJ2026-0203-visual-production.md)、[音频成片](PJ2026-0204-audio-video-production.md)、[流水线平台](PJ2026-0205-pipeline-platform.md) |
| 编号 | 短名 | 主责模块 |
| --- | --- | --- |
| SMF-L0-REQ-006 | 受控发布闭环 | [发布运营](PJ2026-0206-publishing-operations.md) |
智媒工厂应把技术通过的成片组织为可人工批准、可退回和可追溯的发布候选,并生成与最终视频一致的时间轴、来源、AI 参与披露和复核材料。
@@ -406,9 +444,15 @@ Web 工厂和控制台应采用工业风、紧凑高信息密度、直角或极
| 编号 | 短名 | 主责模块 | 关联模块 |
| --- | --- | --- | --- |
| SMF-L0-REQ-007 | 隔离自动交付 | [PJ2026-0207 平台交付](PJ2026-0207-platform-delivery.md) | [流水线平台](PJ2026-0205-pipeline-platform.md)、[发布运营](PJ2026-0206-publishing-operations.md) |
| SMF-L0-REQ-007 | 隔离自动交付 | [平台交付](PJ2026-0207-platform-delivery.md) | [流水线](PJ2026-0205-pipeline-platform.md)、[发布](PJ2026-0206-publishing-operations.md) |
智媒工厂应提供可复现、最小权限和可自动验收的平台交付,使编辑者能通过公网 IP 的 Web 内置 Codex 终端进入与后端版本一致的源码、CLI 和受限 Agent 工具环境
智媒工厂应提供可复现、最小权限和可自动验收的平台交付。
编辑者应能通过公网 IP 的 Web 内置 Codex 终端进入以下环境:
- 与后端版本一致的源码;
- CLI
- 受限 Agent 工具。
平台交付负责:
@@ -433,15 +477,11 @@ Web 工厂和控制台应采用工业风、紧凑高信息密度、直角或极
- 单次作业、一个缺陷、一次样片、一次部署或一次文档收口不进入 L0/L1 需求正文。
- 新增能力先按完成标准确定唯一主责 L1;跨域关系通过同目录相对链接表达,不复制同一需求。
### 7.2 实现引用和追溯
### 7.2 追溯契约
- 本规格当前实现引用版本为 `draft-2026-07-13-p0`
- 后续新增或修改的源码文件应在可承载注释的文件头标记:
- `SPEC: PJ2026-02 智媒工厂 draft-2026-07-13-p0`
- 同时标记主责 L1 的编号、短名和实现引用版本。
- 自动生成文件、第三方文件、纯配置、锁文件和二进制工件可以不加文件头:
- 其生成器、校验器或 owning YAML 入口必须能追溯到本规格;
- 期次工件必须能追溯到作业记录、步骤结果和配置引用摘要。
- 每项全局能力应能追溯到唯一主责 L1 和必要的关联 L1
- 每一期正式工件应能追溯到来源、结构化内容、步骤结果、配置引用摘要和发布版本。
- 自动生成文件、第三方文件、纯配置、锁文件和二进制工件应由对应生成器、校验器或 owning YAML 提供稳定归属。
### 7.3 验收和发布控制
@@ -489,5 +529,9 @@ Web 工厂和控制台应采用工业风、紧凑高信息密度、直角或极
### 7.6 规格回写
- 稳定需求、边界或验收变化回写对应 L1;影响多个 L1 时同时更新本 L0 索引和全局数据流。
- 实现细节、作业编号、日志、截图和长证据保留在项目运行工件,不写入稳定规格正文
- 规格间只使用本目录内的相对链接;不在规格正文保存 GitHub issue 或 PR 链接。
- 执行态内容不写入稳定规格正文
- 实现状态和阶段基线;
- 阻塞和实现细节;
- 源码路径、作业编号、日志、截图和长证据。
- 执行态内容进入阶段报告、任务报告、偏离记录或执行 issue。
- 规格间只使用本目录内的相对链接;不在规格正文保存执行 issue 或代码审查链接。
@@ -2,11 +2,9 @@
## 修改历史
| 版本 | 对应 commit id | 更新日期 | 变更说明 |
| --- | --- | --- | --- |
| v0.1 | 待提交 | 2026-07-13 | 基于现有公开 Feed、Hacker News API、人工来源和期次来源清单建立资讯源规格。 |
当前正文仍在规格治理草稿中;未定稿前不新增版本号,不为单次编辑追加 `待提交` 版本。
| 版本 | 更新日期 | 变更说明 |
| --- | --- | --- |
| v0.1 | 2026-07-13 | 定义公开 Feed、Hacker News、人工来源和期次来源清单的目标能力。 |
## 正文
@@ -19,13 +17,17 @@
| 编号 | PJ2026-0201 |
| 短名 | 资讯源 |
| 层级 | L1 方向 |
| 状态 | 已生效 |
| 实现引用版本 | draft-2026-07-13-p0 |
| 规格状态 | 已生效 |
| 需求规格模板 | [ISO/IEC/IEEE 29148 需求规格模板](../../templates/iso-iec-ieee-29148-requirements-spec-template.md) |
| 上级规格 | [PJ2026-02 智媒工厂总规格](PJ2026-02-smart-media-factory.md) |
| 规格治理索引 | [PJ2026-02 智媒工厂总规格](PJ2026-02-smart-media-factory.md) 第 7 章 |
本文采用 ISO/IEC/IEEE 29148 需求规格模板的项目裁剪版。正文只保留资讯发现、采集、证据、分类、排序和使用边界的稳定需求。
本文采用 ISO/IEC/IEEE 29148 需求规格模板的项目裁剪版。
正文只定义:
- 资讯发现、采集、证据、分类、排序和使用边界的预期终态;
- 对应验收契约。
## 2. 目的和范围
@@ -94,7 +96,7 @@
| PJ2026-020105 | 优惠信息 | 本规格 6.5 | 期限、地区、资格、条件、过期和返利边界 | 官方公告、人工核验 | 编辑策划、发布运营 |
| PJ2026-020106 | 来源治理 | 本规格 6.6 | 归因、预印本、版权、失败和过期来源处置 | 全部来源环节 | 视觉制作、发布运营 |
本章图形描述目标数据面。当前已实现范围和未实现差距见第 7.1 节
本章图形描述资讯源的预期终态数据面
### 5.1 目标数据面架构图
@@ -198,7 +200,7 @@ sequenceDiagram
没有稳定公开入口的来源应声明为 `manual-only`,不能在代码中私自增加隐藏抓取路径。
当前基线覆盖:
来源目录至少应覆盖:
- Arm、NXP、MicroPython、LVGL、Arm-2D 和 PikaPython
- NI、Analog Devices、ST、NVIDIA、Arduino 和嘉立创相关入口;
@@ -223,7 +225,12 @@ sequenceDiagram
- Hacker News 只作为发现来源;
- 不得把提交者或讨论内容改写为项目官方事实。
原始 Feed、Atom、API 响应应按期次存档并带哈希或等价完整性线索。人工来源必须保留登记者可复核的原始链接、日期和短证据,不得只写无法追溯的总结。
原始 Feed、Atom、API 响应应
- 按期次存档;
- 带哈希或等价完整性线索。
人工来源必须保留登记者可复核的原始链接、日期和短证据,不得只写无法追溯的总结。
### 6.3 SOURCE-L1-REQ-003 稳定证据模型
@@ -252,7 +259,12 @@ sequenceDiagram
| --- | --- | --- | --- |
| SOURCE-L1-REQ-004 | 分类排序 | PJ2026-020104 分类排序 | [编辑策划](PJ2026-0202-editorial-planning.md) |
系统应按照栏目 owning YAML 对候选资讯分类和排序,当前优先顺序为嵌入式与 AI、纯嵌入式、AI,并在同类中优先一手来源、明确状态、近期发布和可行动价值
系统应按照栏目 owning YAML 对候选资讯分类和排序。
默认排序规则如下:
- 依次优先嵌入式与 AI、纯嵌入式、AI;
- 同类中优先一手来源、明确状态、近期发布和可行动价值。
分类必须基于来源默认值和内容证据:
@@ -298,21 +310,10 @@ sequenceDiagram
## 7. 过程控制
### 7.1 当前实现状态与引用
### 7.1 追溯契约
- 当前已经实现 YAML 来源目录、Feed/API/人工来源、原始响应、归一化、去重、分类排序和来源清单
- 当前已经在真实 R3 期次中采集第一方、国际媒体、Hacker News 和人工来源
- 第 6 章仍是稳定目标口径;新增来源、许可变化和优惠复核需要持续按该口径验收。
- 来源 owning YAML`/root/selfmedia/config/sources.yaml`
- 栏目分类和排序:`/root/selfmedia/config/editorial.yaml`
- 采集与归一:`/root/selfmedia/scripts/src/steps/collect.ts`
- 期次输入与输出:
- `inputs/manual-sources.yaml`
- `sources/raw/`
- `sources/manifest.json`
- 后续相关源码修改应标记:
- `SPEC: PJ2026-0201 资讯源 draft-2026-07-13-p0`
- 每个候选来源应能追溯到来源目录、采集响应、规范链接、来源角色和期次采用决策
- 自动生成文件、纯配置、锁文件和二进制工件应由对应生成器、校验器或 owning YAML 提供稳定归属
### 7.2 原入口验收
@@ -2,11 +2,9 @@
## 修改历史
| 版本 | 对应 commit id | 更新日期 | 变更说明 |
| --- | --- | --- | --- |
| v0.1 | 待提交 | 2026-07-13 | 基于现有结构化文稿、质量门、专业对象补充和来源闭环建立编辑策划规格。 |
当前正文仍在规格治理草稿中;未定稿前不新增版本号,不为单次编辑追加 `待提交` 版本。
| 版本 | 更新日期 | 变更说明 |
| --- | --- | --- |
| v0.1 | 2026-07-13 | 定义结构化文稿、质量门、专业对象补充和来源闭环的目标能力。 |
## 正文
@@ -19,13 +17,17 @@
| 编号 | PJ2026-0202 |
| 短名 | 编辑策划 |
| 层级 | L1 方向 |
| 状态 | 已生效 |
| 实现引用版本 | draft-2026-07-13-p0 |
| 规格状态 | 已生效 |
| 需求规格模板 | [ISO/IEC/IEEE 29148 需求规格模板](../../templates/iso-iec-ieee-29148-requirements-spec-template.md) |
| 上级规格 | [PJ2026-02 智媒工厂总规格](PJ2026-02-smart-media-factory.md) |
| 规格治理索引 | [PJ2026-02 智媒工厂总规格](PJ2026-02-smart-media-factory.md) 第 7 章 |
本文采用 ISO/IEC/IEEE 29148 需求规格模板的项目裁剪版。正文只定义选题、事实核验、栏目结构、叙事和引用的稳定完成标准。
本文采用 ISO/IEC/IEEE 29148 需求规格模板的项目裁剪版。
正文只定义:
- 选题、事实核验、栏目结构、叙事和引用的预期终态;
- 对应验收契约。
## 2. 目的和范围
@@ -35,14 +37,14 @@
- 把可追溯来源转化为有判断、有信息密度、适合技术受众收听的栏目内容;
- 决定本期为什么讲这些消息;
- 决定每条消息要解释到什么深度以及哪些限制必须明说
- 让开场大纲、故事和总结形成连贯叙事;
- 决定每条消息要解释到什么深度,并把必要事实边界放在相关表述处
- 让开场大纲、逐条故事和参考来源形成连贯叙事;
- 避免把来源摘要机械拼接成“AI 味”文稿。
### 2.2 范围内
- 栏目定位、受众假设、期次题目、候选池、选题数量和内容优先级。
- 固定开场、五十字内概述、卡片大纲、单条故事、总结和参考来源口播结构。
- 固定开场、五十字内概述、卡片大纲、单条故事和参考来源口播结构。
- 事实锚点、日期、数字、产品状态、媒体归因、预印本和活动条件的表达边界。
- 按受众熟悉度补充专业对象的功能、作者、公司、发布者或大学团队来源。
- 结构化文稿、配音文本、视觉方向、来源引用和评论区来源文本的内容一致性。
@@ -66,7 +68,7 @@
| 专业对象补充 | 对陌生库、组件、框架、协议或项目补充其功能和发布者的短句。 |
| 状态限定 | 对 beta、预览、预印本、临时调整、媒体报道、地区限制等事实边界的明确表达。 |
| 镜头意图 | 文稿为视觉制作提供的信息重点、来源页面和画面节奏建议,不是最终视觉实现。 |
| 结构化文稿 | 包含开场大纲、故事、总结、参考来源、配音单元和来源引用关系的机器可读工件。 |
| 结构化文稿 | 包含开场大纲、故事、参考来源、配音单元和来源引用关系的机器可读工件。 |
| 模板模式 | 不调用 LLM、根据来源和固定规则生成可调试文稿的离线路径。 |
## 4. 系统边界和接口
@@ -78,7 +80,7 @@
| 外部使用者 | 栏目编辑、审核者、视觉制作、音频成片和发布运营。 |
| 外部输入 | 期次元数据、来源候选池、栏目规则、受众熟悉对象、人工编辑意见和可选 LLM 响应。 |
| 受控资源 | 选题结果、结构化文稿、质量门反馈、配音单元、镜头意图、来源引用和引用目录。 |
| 外部输出 | 开场大纲、故事、总结、参考来源口播、配音文本、视觉方向和可解析引用。 |
| 外部输出 | 开场大纲、故事、参考来源口播、配音文本、视觉方向和可解析引用。 |
| 用户接口 | TypeScript CLI 文稿单步、可编辑 JSON/TXT/Markdown 工件、日志和 Web 文稿视图。 |
| 系统边界 | 编辑策划负责“说什么以及如何准确地说”;不负责来源可访问性、画面制作、声音表现和最终发布批准。 |
@@ -88,12 +90,12 @@
| --- | --- | --- | --- | --- | --- |
| PJ2026-020201 | 栏目策划 | 本规格 6.1 | 栏目定位、受众、期次结构和选题组合 | 期次元数据、来源候选 | 全部编辑环节 |
| PJ2026-020202 | 事实核验 | 本规格 6.2 | 锚点、状态、数字、日期、归因和不能推出的结论 | 资讯源 | 文稿生成、发布复核 |
| PJ2026-020203 | 口播叙事 | 本规格 6.3 | 开场大纲、故事、总结、节奏和语言质量 | 栏目策划、事实核验 | 视觉制作、音频成片 |
| PJ2026-020203 | 口播叙事 | 本规格 6.3 | 开场大纲、故事、来源转场、节奏和语言质量 | 栏目策划、事实核验 | 视觉制作、音频成片 |
| PJ2026-020204 | 对象补充 | 本规格 6.4 | 专业对象功能、作者或发布者说明与熟悉对象免补充 | 事实核验、受众规则 | 口播叙事 |
| PJ2026-020205 | 文稿质量 | 本规格 6.5 | 生成、重试、质量门、模板路径和人工覆盖 | 全部编辑环节 | 流水线平台 |
| PJ2026-020206 | 引用闭环 | 本规格 6.6 | 文稿到来源、镜头、配音和发布引用的一致关系 | 资讯源、结构化文稿 | 发布运营 |
本章图形描述目标数据面。当前已实现范围和未实现差距见第 7.1 节
本章图形描述编辑策划的预期终态数据面
### 5.1 目标数据面架构图
@@ -134,7 +136,7 @@ flowchart TD
Pool[可追溯候选池] --> Select[按价值与组合选题]
Select --> Evidence[提取事实锚点与限制]
Evidence --> Audience[判断受众熟悉度]
Audience --> Draft[生成开场 / 大纲 / 故事 / 总结]
Audience --> Draft[生成开场大纲 / 故事 / 来源转场]
Draft --> Gate[结构、证据与语言质量门]
Gate -->|拒绝并反馈| Draft
Gate -->|接受| Script[结构化文稿]
@@ -196,7 +198,7 @@ sequenceDiagram
系统不得只选择得分最高或标题最热的若干条。
当前栏目组合规则如下:
默认栏目组合规则如下:
- 优先嵌入式与 AI 交叉;
- 其次纯嵌入式;
@@ -204,7 +206,14 @@ sequenceDiagram
- 避免同一期内容都属于同一厂商、同一产品或同一种发布形式;
- 优惠和供应链变化只有与技术受众实际决策相关时才可入选。
选题完成标准包括来源足够、日期和状态明确、受众价值可以用一句话说明、节目组合有层次。候选证据不足时应减少条目或明确失败,不能用空泛评论补齐数量。
选题完成标准包括
- 来源足够;
- 日期和状态明确;
- 受众价值可以用一句话说明;
- 节目组合有层次。
候选证据不足时应减少条目或明确失败,不能用空泛评论补齐数量。
### 6.2 EDIT-L1-REQ-002 事实与状态核验
@@ -217,10 +226,21 @@ sequenceDiagram
具体事实使用规则如下:
- 日期、版本、处理器、内存、价格、性能和截止时间只有在证据明确时才能出现;
- 来源未提供延迟、功耗、精度、兼容性或开放范围时,文稿应说明未知
- 来源未提供延迟、功耗、精度、兼容性或开放范围时,不得编造相关结论
- 不能用行业常识填成该产品事实。
媒体报道必须说“媒体报道”或点明媒体;大学论文只有在来源明确单位时才说“来自某大学团队”,否则使用“作者团队”;预印本不得暗示已经同行评审。
事实边界表达应满足:
- 必要的状态或适用范围放在相关事实附近;
- 不在每条故事末尾机械添加局限、约束、限制或风险;
- 不使用“仍需验证”类固定收尾句。
媒体和论文归因应满足:
- 媒体报道必须说“媒体报道”或点明媒体;
- 大学论文只有在来源明确单位时才说“来自某大学团队”;
- 来源未明确单位时使用“作者团队”;
- 预印本不得暗示已经同行评审。
### 6.3 EDIT-L1-REQ-003 固定栏目结构
@@ -228,12 +248,13 @@ sequenceDiagram
| --- | --- | --- | --- |
| EDIT-L1-REQ-003 | 口播结构 | PJ2026-020203 口播叙事 | [视觉制作](PJ2026-0203-visual-production.md)、[音频成片](PJ2026-0204-audio-video-production.md) |
系统应生成稳定但不僵硬的栏目结构,至少包含开场卡片式大纲、逐条资讯、总结和参考来源口播。
系统应生成稳定但不僵硬的栏目结构,至少包含合并的开场卡片式大纲、逐条资讯和参考来源口播。
开场应使用期次元数据确定性生成:
- 报出期数、栏目名和日期;
- 使用“主要内容有”引出不超过五十字的概述;
- 在同一开场场景中展示卡片式大纲;
- 使用“下面是详细内容”进入正文;
- 不允许 LLM 改写期数、日期或固定句式。
@@ -241,8 +262,8 @@ sequenceDiagram
- 每条故事先给核心变化;
- 再解释技术或业务影响;
- 最后说明限制、验证建议或下一步
- 总结收束共同主题,不重复逐条摘要
- 以正面、具体、可核验的功能、行动价值或后续动向自然收束
- 最后一条故事结束后直接进入参考来源,不生成总结段或总结场景
- 参考来源口播固定为“以下是这期《栏目名》的参考来源,完整链接见评论区”。
### 6.4 EDIT-L1-REQ-004 受众适配说明
@@ -272,13 +293,19 @@ sequenceDiagram
| --- | --- | --- | --- |
| EDIT-L1-REQ-005 | 文稿质量 | PJ2026-020205 文稿质量 | [流水线平台](PJ2026-0205-pipeline-platform.md)、[音频成片](PJ2026-0204-audio-video-production.md) |
系统应同时提供授权 LLM 和模板模式,并把两者输出为同一结构化文稿契约。LLM 只接收任务所需的来源证据和栏目规则,凭据不能进入提示词、工件或日志。
系统应同时提供授权 LLM 和模板模式,并把两者输出为同一结构化文稿契约。
LLM 输入边界如下:
- 只接收任务所需的来源证据和栏目规则;
- 凭据不能进入提示词、工件或日志。
质量门至少检查:
- 固定结构、概述长度和故事数量;
- 引用可解析、事实锚点和状态限定;
- 专业对象补充、空泛套话和后台术语;
- 机械的局限、约束、限制、风险或“仍需验证”类结尾;
- 不适合口播的格式。
失败时应保存拒绝原因,并把具体反馈用于下一次尝试。
@@ -289,7 +316,9 @@ sequenceDiagram
| 编号 | 短名 | 主责模块 | 关联模块 |
| --- | --- | --- | --- |
| EDIT-L1-REQ-006 | 引用闭环 | PJ2026-020206 引用闭环 | [资讯源](PJ2026-0201-information-sources.md)、[视觉制作](PJ2026-0203-visual-production.md)、[发布运营](PJ2026-0206-publishing-operations.md) |
| EDIT-L1-REQ-006 | 引用闭环 | PJ2026-020206 引用闭环 | 资讯、视觉、发布 |
关联模块为[资讯](PJ2026-0201-information-sources.md)、[视觉](PJ2026-0203-visual-production.md)和[发布](PJ2026-0206-publishing-operations.md)。
系统应为每条故事保留:
@@ -297,38 +326,32 @@ sequenceDiagram
- 中文短释、镜头意图和配音单元;
- 可关联文稿、来源画面、字幕、章节时间轴和评论区引用的故事身份。
引用目录应只列实际采用的来源,不把候选池全部条目混入。发现来源与一手来源并存时必须保持各自角色,不能在引用阶段丢失发现关系或重复计算同一事实。
引用目录应只列实际采用的来源,不把候选池全部条目混入。
发现来源与一手来源并存时必须:
- 保持各自角色;
- 不在引用阶段丢失发现关系;
- 不重复计算同一事实。
结构化文稿是下游内容真相源。纯文本配音稿、引用 Markdown 和评论区文本应由它和最终场景时间线生成,不应由人工复制粘贴形成互相漂移的版本。
## 7. 过程控制
### 7.1 当前实现状态与引用
### 7.1 追溯契约
- 当前已经实现授权 LLM、模板模式、确定性开场、质量门、专业对象补充、引用和配音单元
- 当前 R3 文稿已经生成开场、大纲、五条故事、总结和参考来源口播
- 第 6 章描述持续目标;人工批准和发布版本不由本 L1 当前实现提供。
- 栏目和文稿 owning YAML`/root/selfmedia/config/editorial.yaml`
- 文稿生成与质量门:`/root/selfmedia/scripts/src/steps/script.ts`
- 评论区来源:`/root/selfmedia/scripts/src/comment-reference.ts`
- 期次工件:
- `script/episode.json`
- `script/narration.txt`
- `script/citations.md`
- `script/comment-reference.txt`
- 后续相关源码修改应标记:
- `SPEC: PJ2026-0202 编辑策划 draft-2026-07-13-p0`
- 每条结构化内容应能追溯到采用来源、事实锚点、来源角色、编辑决策和下游故事身份
- 自动生成文件、纯配置、锁文件和二进制工件应由对应生成器、校验器或 owning YAML 提供稳定归属
### 7.2 原入口验收
- 通过 TypeScript CLI 单步运行 `script`
- 授权 LLM 路径应记录模型、请求和用量工件;
- `--template-only` 路径应不读取 LLM 凭据。
- 核对开场期数、栏目名、日期、五十字内概述和固定转场句。
- 核对开场期数、栏目名、日期、五十字内概述、卡片大纲和固定转场句。
- 核对每条故事的事实锚点、来源角色、状态限定、专业对象补充和未知信息边界。
- 核对引用可在期次来源清单中解析,配音单元和故事顺序一致。
- 通过人工朗读抽检口语节奏、信息密度“AI 味”套话。
- 通过人工朗读抽检口语节奏、信息密度“AI 味”套话和机械限制型结尾
### 7.3 质量门和人工覆盖
@@ -2,11 +2,9 @@
## 修改历史
| 版本 | 对应 commit id | 更新日期 | 变更说明 |
| --- | --- | --- | --- |
| v0.1 | 待提交 | 2026-07-13 | 基于来源页面视觉、暖色 HTML 动效、阶段导航、PNG 和 PPTX 能力建立视觉制作规格。 |
当前正文仍在规格治理草稿中;未定稿前不新增版本号,不为单次编辑追加 `待提交` 版本。
| 版本 | 更新日期 | 变更说明 |
| --- | --- | --- |
| v0.1 | 2026-07-13 | 定义来源页面视觉、HTML 动效、阶段导航、PNG 和 PPTX 的目标能力。 |
## 正文
@@ -19,13 +17,17 @@
| 编号 | PJ2026-0203 |
| 短名 | 视觉制作 |
| 层级 | L1 方向 |
| 状态 | 已生效 |
| 实现引用版本 | draft-2026-07-13-p0 |
| 规格状态 | 已生效 |
| 需求规格模板 | [ISO/IEC/IEEE 29148 需求规格模板](../../templates/iso-iec-ieee-29148-requirements-spec-template.md) |
| 上级规格 | [PJ2026-02 智媒工厂总规格](PJ2026-02-smart-media-factory.md) |
| 规格治理索引 | [PJ2026-02 智媒工厂总规格](PJ2026-02-smart-media-factory.md) 第 7 章 |
本文采用 ISO/IEC/IEEE 29148 需求规格模板的项目裁剪版。正文只定义来源视觉、场景、主题、动效、演示文稿和视觉验收的稳定需求。
本文采用 ISO/IEC/IEEE 29148 需求规格模板的项目裁剪版。
正文只定义:
- 来源视觉、场景、主题、动效、演示文稿和视觉验收的预期终态;
- 对应验收契约。
## 2. 目的和范围
@@ -35,14 +37,16 @@
- 把结构化文稿和来源证据转化为信息清楚、节奏连贯的动态画面;
- 形成具有栏目辨识度的视觉语言;
- 生成的 HTML、PPT、PNG、封面和视频采用温暖、阳光、圆润的科技编辑部方向;
- 生成的 HTML、PPT、PNG、封面和视频采用白色与绿色为主、温暖、阳光、圆润的科技编辑部方向;
- 避免冷冽的蓝黑仪表盘观感;
- 长期支持栏目主题切换,同时保持来源可追溯、绝对时间可复现和多工件一致。
- 支持栏目主题切换,同时保持来源可追溯、绝对时间可复现和多工件一致。
### 2.2 范围内
- 入选资讯原始网页、GitHub 页面或论文摘要页的本地视觉采集和失败降级。
- 开场卡片大纲、逐条资讯、总结和参考来源等场景类型。
- 合并开场卡片大纲、逐条资讯和参考来源等场景类型。
- 本地开源 OCR、画面信息量分析、LLM 画面筛选和可审查的重点区域规划。
- Web 人工调整重点区域、出现时段和放大比例的控制入口。
- YAML 可配置的色彩、圆角、阴影、纹理、字号、留白、字幕安全区和章节导航。
- HTML/CSS/JavaScript 动效、来源页面向下滚动、绝对时间寻址和逐帧渲染状态。
- 顶部阶段内细进度、底部总阶段导航和总进度的对齐与跳转语义。
@@ -70,7 +74,9 @@
| 绝对时间寻址 | 通过场景内毫秒位置直接计算动效状态,使任意帧可重现而不依赖前序播放。 |
| 阶段内进度 | 位于画面顶部、表示当前场景内部播放位置的细进度条。 |
| 总阶段导航 | 位于画面底部、等宽等高展示整期短标题并与总进度 X 轴对齐的章节导航。 |
| 来源滚动 | 在若干本地来源帧之间按绝对时间确定性过渡,形成页面向下滚动的视觉效果。 |
| 来源滚动 | 单个页面按绝对时间从上到下滚动,完成后直接切换下一页面的确定性视觉效果。 |
| OCR 画面证据 | 本地开源 OCR 从候选画面提取的文本、位置、布局和置信度,用于筛选与重点区域验证。 |
| 重点区域 | 在来源画面中可由 OCR 和来源事实验证,或由编辑者手工指定的放大矩形、出现时段和倍率。 |
| 内容主题令牌 | 由 YAML 管理的生成内容颜色、圆角、阴影、纹理和布局参数。 |
| 字幕安全区 | 为字幕和播放器覆盖层预留、不得放置关键信息的画面区域。 |
| 无图降级 | 来源页面不可采集或不适合使用时,以原创文字、数据卡片和来源标识替代第三方画面。 |
@@ -92,14 +98,14 @@
| 编号 | 模块或课题 | 规格位置 | 主责边界 | 上游依赖 | 下游支撑 |
| --- | --- | --- | --- | --- | --- |
| PJ2026-020301 | 来源画面 | 本规格 6.1 | 页面采集、滚动位置、清单、失败和无图降级 | 资讯源、结构化文稿 | 场景编排 |
| PJ2026-020302 | 场景编排 | 本规格 6.2 | 开场大纲、故事、总结、来源页和章节身份 | 编辑策划、来源画面 | 动效渲染、音频成片 |
| PJ2026-020301 | 来源画面 | 本规格 6.1 | 页面采集、OCR、画面筛选、滚动位置、清单、失败和无图降级 | 资讯源、结构化文稿 | 场景编排 |
| PJ2026-020302 | 场景编排 | 本规格 6.2 | 开场大纲、故事、来源页和章节身份 | 编辑策划、来源画面 | 动效渲染、音频成片 |
| PJ2026-020303 | 主题系统 | 本规格 6.3 | 色彩、圆角、阴影、纹理、字号、留白和主题切换 | YAML、栏目定位 | 全部场景 |
| PJ2026-020304 | 导航进度 | 本规格 6.4 | 阶段内进度、总导航、总进度、对齐和标题高度 | 场景编排、时间线 | 成片、Web 跳转 |
| PJ2026-020305 | 动效渲染 | 本规格 6.5 | 绝对时间寻址、来源滚动、逐帧一致性和离线渲染 | 场景编排、主题系统 | 音频成片 |
| PJ2026-020305 | 动效渲染 | 本规格 6.5 | 绝对时间寻址、来源滚动、重点放大、逐帧一致性和离线渲染 | 场景编排、主题系统 | 音频成片 |
| PJ2026-020306 | 视觉工件 | 本规格 6.6 | HTML、PNG、PPTX、封面和视觉质量边界 | 全部视觉环节 | 发布运营 |
本章图形描述目标数据面。当前已实现范围和未实现差距见第 7.1 节
本章图形描述视觉制作的预期终态数据面
### 5.1 目标数据面架构图
@@ -113,13 +119,19 @@ flowchart LR
subgraph VisualPlane[视觉制作数据面]
Capture[来源页面采集器]
VisualStore[本地来源帧与清单]
OCR[本地 OCR]
Planner[画面筛选与重点规划]
SceneCompiler[场景编排器]
HtmlRenderer[HTML 动效渲染器]
DeckRenderer[PNG / PPTX 渲染器]
end
Sources --> Capture
Capture --> VisualStore
VisualStore --> OCR
OCR --> Planner
Script --> SceneCompiler
Script --> Planner
Planner --> SceneCompiler
VisualStore --> SceneCompiler
Theme --> SceneCompiler
SceneCompiler --> HtmlRenderer
@@ -137,7 +149,9 @@ flowchart TD
Script[结构化文稿] --> Scenes[场景编排]
Sources[来源清单] --> Capture[来源页面采集]
Capture --> VisualManifest[来源视觉清单]
VisualManifest --> Scenes
VisualManifest --> OCR[本地 OCR 与布局]
OCR --> Plan[画面筛选与重点区域计划]
Plan --> Scenes
Theme[主题 owning YAML] --> HTML[HTML 场景生成]
Scenes --> HTML
HTML --> Preview[PNG 预览]
@@ -187,7 +201,38 @@ sequenceDiagram
| --- | --- | --- | --- |
| VISUAL-L1-REQ-001 | 来源画面 | PJ2026-020301 来源画面 | [资讯源](PJ2026-0201-information-sources.md)、[编辑策划](PJ2026-0202-editorial-planning.md) |
系统应为每条入选资讯按来源视觉策略采集本地画面,并在视觉清单中保留故事、来源、链接、页面标题、采集状态、画面路径、滚动位置、页面高度和失败原因
系统应为每条入选资讯按来源视觉策略采集本地画面。
视觉清单应保留:
- 故事、来源、链接和页面标题;
- 采集状态和失败原因;
- 画面路径、滚动位置和页面高度。
候选画面必须经过本地开源 OCR,至少登记:
- 识别文本、文本框、行和布局;
- 置信度、画面尺寸和 OCR 模型版本;
- 可与来源事实匹配的关键词和重点区域候选。
授权 LLM 可以消费:
- OCR 结果;
- 来源事实;
- 候选画面摘要。
LLM 输出应包含:
- 画面选择和理由;
- 重点矩形;
- 出现时段;
- 放大倍率。
自动重点放大应满足:
- 只有 OCR 或来源事实能够验证时才能生效;
- 置信不足时标记为人工确认;
- 允许编辑者在 Web 中手工调整。
来源画面可以按网页、GitHub 和论文摘要使用不同样式,但必须遵守:
@@ -206,16 +251,15 @@ sequenceDiagram
系统应把结构化文稿编排为具有稳定身份和章节语义的场景:
- 当前栏目至少包含封面开场卡片大纲、每条资讯、总结和参考来源;
- 栏目至少包含合并的开场卡片大纲、每条资讯和参考来源;
- 故事数量变化时,场景数量应按内容生成;
- 场景数量不能写死为九页。
各场景职责如下:
- 开场突出期数、日期和主题;
- 大纲使用可快速扫读的卡片;
- 开场突出期数、日期和主题,并在同一场景使用可快速扫读的卡片大纲
- 故事页呈现短标题、核心变化、关键信息和来源画面;
- 总结收束主题
- 最后一条故事结束后直接进入参考来源
- 参考来源页以类似参考文献的编号格式列出实际采用来源。
场景标题、章节短标题、中文短释和来源身份应来自同一结构化文稿,不允许在 HTML、PPTX 和视频中分别人工维护。
@@ -226,7 +270,15 @@ sequenceDiagram
| --- | --- | --- | --- |
| VISUAL-L1-REQ-003 | 暖阳主题 | PJ2026-020303 主题系统 | [编辑策划](PJ2026-0202-editorial-planning.md)、[发布运营](PJ2026-0206-publishing-operations.md) |
系统应通过 owning YAML 管理生成内容主题令牌。当前默认主题使用米白、琥珀、陶土和暖棕色,大圆角、柔和阴影和轻纸纹营造温暖、阳光的编辑部感受。
系统应通过 owning YAML 管理生成内容主题令牌。
默认主题应满足:
- 以纯白为大面积底色;
- 以中深绿色作为框线、点缀、导航和装饰;
- 通过暖白、圆角、克制阴影和充分留白营造温暖、阳光的编辑部感受。
默认主题不得使用渐变。页面层级应通过边框、色块、字号、间距和少量实色强调建立,不得用大面积渐变制造科技感。
本主题只用于生成的 HTML、PPT、PNG、封面和视频。Web 工厂、控制台与内置终端不得继承这些大圆角、卡片留白和暖色装饰规则。
@@ -237,7 +289,7 @@ sequenceDiagram
- 不让暖色退化为所有元素同色;
- 不依赖颜色单独表达状态。
主题切换不得改变场景数据、来源关系和时间线契约。未来栏目可以有其他主题,但生产 HTML 不得依赖在线字体、脚本或样式服务。
主题切换不得改变场景数据、来源关系和时间线契约。其他栏目可以选择不同主题,但生产 HTML 不得依赖在线字体、脚本或样式服务。
### 6.4 VISUAL-L1-REQ-004 对齐章节导航
@@ -255,11 +307,20 @@ sequenceDiagram
阶段导航必须满足:
- 所有阶段等宽、等高;
- 底部总览使用直角并与画面底边贴合;
- 相邻阶段之间不留空隙;
- X 轴从画面最左侧延伸到最右侧,并与总进度起止位置完全对齐;
- 导航内只显示简短标题;
- 标题过长时允许换行,并统一增加全部阶段的 Y 轴高度;
- 不能截断、使用省略号或让单个阶段独自变高;
- 标题长度和导航最大高度由配置给出上限。
底部总览颜色应满足:
- 过去、当前和未来阶段使用具有足够对比度的实色;
- 活动阶段和结束阶段在典型视频压缩和普通显示器上仍可清楚区分;
- 不使用过浅底色。
场景数量和标题来自当期场景模型。HTML 预览和 Web 编辑台可以点击章节跳转,成片中的导航则以高亮和进度提供同一视觉语义。
### 6.5 VISUAL-L1-REQ-005 确定性动态画面
@@ -270,9 +331,28 @@ sequenceDiagram
系统应以场景内绝对时间计算所有生产动效,使从任意时间点渲染的画面与从头播放到该点的画面一致。
来源页面滚动应使用本地采集帧和确定性插值形成向下浏览效果。进场、强调、进度和卡片动效应服务信息理解,不能造成文字晃动、阅读时间不足或字幕冲突。
来源页面滚动顺序如下:
浏览器渲染必须在禁用后台网络依赖的生产环境中运行。指定画布、捕获帧率和时间点应产生稳定尺寸与场景身份;中断重跑不得因上次浏览器状态改变结果。
- 先直接显示一个页面;
- 在该页面内从上到下滚动;
- 滚动完成后直接切换到下一页面;
- 页面之间不得使用会让两页文字叠加的慢渐变。
重点放大应满足:
- 使用视觉计划中的矩形、时段和倍率;
- 保持右侧来源画面文字可读;
- 自动计划未通过 OCR 或来源事实验证时不得静默启用;
- 使用 Web 人工调整结果或保持未放大画面。
进场、强调、进度和卡片动效应服务信息理解,不能造成文字晃动、阅读时间不足或字幕冲突。
浏览器渲染必须在禁用后台网络依赖的生产环境中运行。
渲染应满足:
- 指定画布、捕获帧率和时间点产生稳定尺寸与场景身份;
- 中断重跑不得因上次浏览器状态改变结果。
### 6.6 VISUAL-L1-REQ-006 多工件视觉交付
@@ -289,32 +369,32 @@ sequenceDiagram
- PNG 应支持人工快速抽检和无浏览器场景预览;
- 封面候选应由栏目视觉生成,不复制来源站点封面。
视觉交付完成标准包括关键文字不越界、字幕安全区无关键信息、来源标识清楚、长标题可读、参考来源页可辨识、无意外网络依赖和无未登记第三方资产。
视觉交付完成标准包括
- 关键文字不越界;
- 字幕安全区无关键信息;
- 来源标识清楚;
- 长标题可读;
- 参考来源页可辨识;
- 无意外网络依赖;
- 无未登记第三方资产。
## 7. 过程控制
### 7.1 当前实现状态与引用
### 7.1 追溯契约
- 当前已经实现来源页面采集、无图降级、暖色 HTML 动效、PNG、PPTX 和双层进度导航
- 当前 R3 已生成九个场景和入选资讯的本地滚动页面帧
- 第 6 章描述稳定目标;多栏目主题治理和正式素材授权台账仍需后续实现。
- 主题与场景配置:`/root/selfmedia/config/editorial.yaml``config/tools.yaml`
- 来源视觉采集:`/root/selfmedia/scripts/src/steps/visuals.ts`
- 场景和演示文稿:`/root/selfmedia/scripts/src/steps/slides.ts`
- HTML 生成与浏览器渲染:
- `/root/selfmedia/scripts/src/slide-html.ts`
- `/root/selfmedia/scripts/src/slide-browser.ts`
- 期次工件:`visuals/``slides/html/``slides/png/``slides/deck.pptx``output/cover.png`
- 后续相关源码修改应标记:
- `SPEC: PJ2026-0203 视觉制作 draft-2026-07-13-p0`
- 每个视觉场景应能追溯到故事身份、来源许可、画面策略、视觉计划和最终时间线
- 自动生成文件、纯配置、锁文件和二进制工件应由对应生成器、校验器或 owning YAML 提供稳定归属
### 7.2 原入口验收
- 通过 TypeScript CLI 分别单步运行来源视觉和幻灯步骤。
- 核对每条入选资讯的视觉状态、来源链接、帧数、滚动位置和失败降级。
- 在浏览器中检查开场大纲、至少两条故事、总结和来源页的开始、中间、结束状态。
- 在浏览器中检查合并开场大纲、至少两条故事和来源页的开始、中间、结束状态。
- 核对 OCR 文本和布局、LLM 画面选择理由、自动重点区域验证与 Web 人工调整结果。
- 检查顶部细进度、底部总导航、总进度 X 轴对齐、阶段等宽等高和长标题换行。
- 检查底部总览直角贴边、阶段无间隙、全宽对齐和深色对比度。
- 检查单页向下滚动完成后直接切页,页面之间不存在慢渐变文字重叠。
- 核对 HTML、PNG、PPTX 的场景数、顺序、尺寸、标题和来源一致。
- 在桌面和窄屏 Web 入口检查章节导航可点击且无横向溢出。
@@ -2,11 +2,9 @@
## 修改历史
| 版本 | 对应 commit id | 更新日期 | 变更说明 |
| --- | --- | --- | --- |
| v0.1 | 待提交 | 2026-07-13 | 基于本地 Kokoro 配音、可调语速、字幕时间线、FFmpeg 成片和自动检查建立音频成片规格。 |
当前正文仍在规格治理草稿中;未定稿前不新增版本号,不为单次编辑追加 `待提交` 版本。
| 版本 | 更新日期 | 变更说明 |
| --- | --- | --- |
| v0.1 | 2026-07-13 | 定义本地配音、可调语速、字幕时间线、成片和自动检查的目标能力。 |
## 正文
@@ -19,13 +17,17 @@
| 编号 | PJ2026-0204 |
| 短名 | 音频成片 |
| 层级 | L1 方向 |
| 状态 | 已生效 |
| 实现引用版本 | draft-2026-07-13-p0 |
| 规格状态 | 已生效 |
| 需求规格模板 | [ISO/IEC/IEEE 29148 需求规格模板](../../templates/iso-iec-ieee-29148-requirements-spec-template.md) |
| 上级规格 | [PJ2026-02 智媒工厂总规格](PJ2026-02-smart-media-factory.md) |
| 规格治理索引 | [PJ2026-02 智媒工厂总规格](PJ2026-02-smart-media-factory.md) 第 7 章 |
本文采用 ISO/IEC/IEEE 29148 需求规格模板的项目裁剪版。正文只定义本地配音、时间线、字幕、音视频合成和媒体质量的稳定需求。
本文采用 ISO/IEC/IEEE 29148 需求规格模板的项目裁剪版。
正文只定义:
- 本地配音、时间线、字幕、音视频合成和媒体质量的预期终态;
- 对应验收契约。
## 2. 目的和范围
@@ -60,7 +62,7 @@
| 术语 | 定义 |
| --- | --- |
| 配音单元 | 与开场大纲、故事、总结或来源场景对应的结构化可朗读文本。 |
| 配音单元 | 与开场大纲、故事或来源场景对应的结构化可朗读文本。 |
| 原始语音缓存 | 按文本、模型、音色和缓存版本索引的 TTS 原始推理结果。 |
| 后处理语速 | 在原始 TTS 输出后以确定倍率调整时长的音频处理参数。 |
| 句级时间线 | 每个配音分段的场景身份、开始、结束、停顿和音频路径记录。 |
@@ -93,7 +95,7 @@
| PJ2026-020405 | 媒体检查 | 本规格 6.5 | 流、尺寸、时长、响度、峰值、解码和工件完整性 | 全部媒体环节 | 发布运营 |
| PJ2026-020406 | 媒体重跑 | 本规格 6.6 | 参数影响范围、缓存复用和单步重新生成 | 流水线平台 | 编辑调试 |
本章图形描述目标数据面。当前已实现范围和未实现差距见第 7.1 节
本章图形描述音频成片的预期终态数据面
### 5.1 目标数据面架构图
@@ -196,7 +198,22 @@ sequenceDiagram
- 不改写事实或把技术标识变成错误词义;
- 在读音不确定时提供人工词表或改稿入口。
声音选择应优先自然、清晰和有轻微表现力,避免长篇机械平读。系统应允许未来替换更新的本地模型,但模型许可、文件哈希、运行资源和音色兼容性必须可复核。
声音选择应满足:
- 自然、清晰并具有轻微表现力;
- 避免长篇机械平读;
- 允许替换更新的本地模型;
- 模型许可、文件哈希、运行资源和音色兼容性可复核。
默认后处理语速应为所选模型自然基线的 `1.2x`,并允许通过 owning YAML 调整。
修改语速后只重跑:
- 配音后处理;
- 时间线;
- 字幕;
- 渲染;
- 检查。
### 6.2 MEDIA-L1-REQ-002 精确音频时间线
@@ -218,9 +235,20 @@ sequenceDiagram
系统应从句级时间线和实际配音文本生成 SRT 与 ASS,使两种字幕的文本、顺序和场景关系一致,并在格式量化允许误差内保持时间同步。
字幕切分应由最大字符数、最小字符数和最大行数约束,优先按中文标点和语义边界断句。长技术标识不应被拆到难以理解的位置,短词不能孤立闪现。
字幕切分应
字幕样式应与视觉主题协调并位于安全区,具备足够字号、描边和背景对比。最后一条字幕应在音频结尾前保留配置的尾部停顿,不覆盖片尾自然结束。
- 由最大字符数、最小字符数和最大行数约束;
- 优先按中文标点和语义边界断句;
- 不把长技术标识拆到难以理解的位置;
- 不让短词孤立闪现。
字幕样式应:
- 与视觉主题协调;
- 位于安全区;
- 具备足够字号、描边和背景对比。
最后一条字幕应在音频结尾前保留配置的尾部停顿,不覆盖片尾自然结束。
### 6.4 MEDIA-L1-REQ-004 确定性视频合成
@@ -252,7 +280,14 @@ sequenceDiagram
- 声明工件存在和来源引用可解析;
- 响度、真峰值和字幕尾部停顿。
检查应使用实际 MP4、WAV、字幕、场景时间线、工件清单、结构化文稿和来源清单,不能只验证作业返回成功。必要时应完整解码候选视频以发现尾部损坏或编码中断。
检查应使用
- 实际 MP4 和 WAV
- 字幕和场景时间线;
- 工件清单;
- 结构化文稿和来源清单。
检查不能只验证作业返回成功。必要时应完整解码候选视频,以发现尾部损坏或编码中断。
所有必要检查为真只表示候选满足技术门槛。检查报告必须保留人工复核提示,明确读音、字幕观感、视觉版权、事实和平台合规仍需发布者判断。
@@ -264,26 +299,23 @@ sequenceDiagram
系统应支持配音、字幕、渲染和检查分别单步重跑,并依据参数影响范围复用安全缓存。
原始语音缓存键必须包含文本、模型、声音和缓存版本。只改后处理语速、停顿或响度时可复用原始 TTS;改变文本、模型、声音或规范化语义时必须失效相关缓存。
原始语音缓存键必须包含
- 文本;
- 模型;
- 声音;
- 缓存版本。
只改后处理语速、停顿或响度时可复用原始 TTS。改变文本、模型、声音或规范化语义时必须失效相关缓存。
每次重跑应生成新的作业记录和结果摘要。正式工件可以更新为最新候选,但历史作业、成本和失败证据不得被改写为同一次成功。
## 7. 过程控制
### 7.1 当前实现状态与引用
### 7.1 追溯契约
- 当前已经实现本地 Kokoro 配音、缓存、可调语速、响度、句级时间线、SRT/ASS、FFmpeg 成片和十项技术检查
- 当前检查结果只说明媒体技术门槛通过,不提供事实、版权或发布批准状态
- 第 6 章描述可替换模型和持续媒体质量的目标口径。
- 媒体 owning YAML`/root/selfmedia/config/tools.yaml`
- 配音:`/root/selfmedia/scripts/src/steps/voice.ts``tools/kokoro_tts.py`
- 字幕:`/root/selfmedia/scripts/src/steps/subtitles.ts`
- 渲染:`/root/selfmedia/scripts/src/steps/render.ts``scripts/src/slide-browser.ts`
- 检查:`/root/selfmedia/scripts/src/steps/inspect.ts`
- 期次工件:`audio/``subtitles/``output/episode.mp4``output/scene-timeline.json``output/inspection.json`
- 后续相关源码修改应标记:
- `SPEC: PJ2026-0204 音频成片 draft-2026-07-13-p0`
- 每个音频、字幕和视频工件应能追溯到配音单元、场景时间线、媒体配置和候选版本
- 自动生成文件、纯配置、锁文件和二进制工件应由对应生成器、校验器或 owning YAML 提供稳定归属
### 7.2 原入口验收
@@ -2,11 +2,9 @@
## 修改历史
| 版本 | 对应 commit id | 更新日期 | 变更说明 |
| --- | --- | --- | --- |
| v0.1 | 待提交 | 2026-07-13 | 基于 YAML-first、TypeScript CLI、异步作业、用量计费和 Web 编辑台建立流水线平台规格。 |
当前正文仍在规格治理草稿中;未定稿前不新增版本号,不为单次编辑追加 `待提交` 版本。
| 版本 | 更新日期 | 变更说明 |
| --- | --- | --- |
| v0.1 | 2026-07-13 | 定义 YAML-first、TypeScript CLI、异步作业、用量计费和 Web 编辑台的目标能力。 |
## 正文
@@ -19,13 +17,12 @@
| 编号 | PJ2026-0205 |
| 短名 | 流水线平台 |
| 层级 | L1 方向 |
| 状态 | 已生效 |
| 实现引用版本 | draft-2026-07-13-p0 |
| 规格状态 | 已生效 |
| 需求规格模板 | [ISO/IEC/IEEE 29148 需求规格模板](../../templates/iso-iec-ieee-29148-requirements-spec-template.md) |
| 上级规格 | [PJ2026-02 智媒工厂总规格](PJ2026-02-smart-media-factory.md) |
| 规格治理索引 | [PJ2026-02 智媒工厂总规格](PJ2026-02-smart-media-factory.md) 第 7 章 |
本文采用 ISO/IEC/IEEE 29148 需求规格模板的项目裁剪版。正文只定义配置、调度、作业、工件、计量和可视控制面的稳定运行能力
本文采用 ISO/IEC/IEEE 29148 需求规格模板的项目裁剪版。正文只定义配置、调度、作业、工件、计量和可视控制面的预期终态与验收契约
## 2. 目的和范围
@@ -96,7 +93,7 @@
| PJ2026-020505 | 用量成本 | 本规格 6.5 | LLM 请求用量、公开目录价、质量门结果和汇总 | 编辑策划、价格 YAML | CLI、Web、发布复核 |
| PJ2026-020506 | 可视控制 | 本规格 6.6 | 同源 API、Web、媒体预览、服务生命周期和公网入口 | 作业事实、工件事实 | 编辑者和审核者 |
本章图形描述目标数据面。当前已实现范围和未实现差距见第 7.1 节
本章图形描述流水线平台的预期终态数据面
### 5.1 目标数据面架构图
@@ -173,13 +170,27 @@ sequenceDiagram
### 6.1 PLATFORM-L1-REQ-001 YAML-first 配置
| 编号 | 短名 | 主责模块 | 关联模块 |
| --- | --- | --- | --- |
| PLATFORM-L1-REQ-001 | 配置治理 | PJ2026-020501 配置治理 | [资讯源](PJ2026-0201-information-sources.md)、[编辑策划](PJ2026-0202-editorial-planning.md)、[视觉制作](PJ2026-0203-visual-production.md)、[音频成片](PJ2026-0204-audio-video-production.md) |
| 编号 | 短名 | 主责模块 |
| --- | --- | --- |
| PLATFORM-L1-REQ-001 | 配置治理 | PJ2026-020501 配置治理 |
关联模块包括:
- [资讯源](PJ2026-0201-information-sources.md)
- [编辑策划](PJ2026-0202-editorial-planning.md)
- [视觉制作](PJ2026-0203-visual-production.md)
- [音频成片](PJ2026-0204-audio-video-production.md)。
系统应以一个配置入口通过显式 `configRefs` 引用各职责 owning YAML,并在运行前校验版本、类型、必填字段、枚举、数值范围、路径和引用摘要。
可调业务事实只能由 owning YAML 定义。代码可以提供模式和渲染逻辑,但不得在字段缺失时静默选择另一套模型、来源、端口、语速、视觉主题、质量门或价格。
可调业务事实只能由 owning YAML 定义。
代码可以提供模式和渲染逻辑,但不得在字段缺失时静默选择另一套:
- 模型或来源;
- 端口或语速;
- 视觉主题或质量门;
- 价格。
配置计划和依赖计划必须:
@@ -198,7 +209,7 @@ Secret 只允许通过受控引用在运行时读取,计划输出不得显示
系统应把以下能力定义为正式步骤:
- 采集、文稿、来源视觉和幻灯;
- 采集、文稿、来源视觉、本地 OCR、画面规划和幻灯;
- 配音、字幕、渲染和检查。
每个步骤必须有明确上游工件、下游工件、进度、日志、结果和失败契约,并可以通过 TypeScript CLI 独立运行。
@@ -219,7 +230,12 @@ Secret 只允许通过受控引用在运行时读取,计划输出不得显示
| --- | --- | --- | --- |
| PLATFORM-L1-REQ-003 | 作业运行 | PJ2026-020503 作业运行 | 全部 L1 |
所有耗时生产命令应采用 Fire-and-Forget提交后立即返回作业标识,后台执行,用户通过状态、日志和结果观察,不在 CLI 前台等待网络、LLM、TTS、浏览器或编码完成。
所有耗时生产命令应采用 Fire-and-Forget
- 提交后立即返回作业标识;
- 在后台执行;
- 用户通过状态、日志和结果观察;
- 不在 CLI 前台等待网络、LLM、TTS、浏览器或编码完成。
作业事实至少包含:
@@ -237,7 +253,14 @@ Secret 只允许通过受控引用在运行时读取,计划输出不得显示
| --- | --- | --- | --- |
| PLATFORM-L1-REQ-004 | 工件事实 | PJ2026-020504 工件事实 | [发布运营](PJ2026-0206-publishing-operations.md)、全部生产 L1 |
系统应以安全期次标识隔离每一期输入和输出,并保存标题、期数、报道日期和创建时间。正式工件路径应固定、可枚举、可校验,禁止路径穿越和跨期次隐式读取。
系统应以安全期次标识隔离每一期输入和输出,并保存
- 标题;
- 期数;
- 报道日期;
- 创建时间。
正式工件路径应固定、可枚举、可校验,禁止路径穿越和跨期次隐式读取。
步骤完成时应登记:
@@ -254,7 +277,13 @@ Secret 只允许通过受控引用在运行时读取,计划输出不得显示
| --- | --- | --- | --- |
| PLATFORM-L1-REQ-005 | 用量成本 | PJ2026-020505 用量成本 | [编辑策划](PJ2026-0202-editorial-planning.md)、[发布运营](PJ2026-0206-publishing-operations.md) |
系统应为每次 LLM 请求记录期次、作业、尝试、请求标识、请求模型、响应模型、服务层、时间、HTTP 状态、用量状态、Token 明细、质量门结果和费用估算。
系统应为每次 LLM 请求记录
- 期次、作业、尝试和请求标识;
- 请求模型、响应模型和服务层;
- 时间和 HTTP 状态;
- 用量状态和 Token 明细;
- 质量门结果和费用估算。
Token 明细至少区分:
@@ -277,7 +306,13 @@ Token 明细至少区分:
| --- | --- | --- | --- |
| PLATFORM-L1-REQ-006 | 可视控制 | PJ2026-020506 可视控制 | [发布运营](PJ2026-0206-publishing-operations.md)、全部生产 L1 |
系统应在 CLI 完整流程可用后提供前后端可视控制面,通过同源 API 展示期次、步骤 DAG、作业、进度、日志、错误、用量和全部工件,并支持与 CLI 等价的受控动作
系统应在 CLI 完整流程可用后提供前后端可视控制面。
可视控制面应通过同源 API
- 展示期次、步骤 DAG、作业和进度;
- 展示日志、错误、用量和全部工件;
- 支持与 CLI 等价的受控动作。
Web 工厂和控制台应采用工业风视觉:
@@ -287,6 +322,22 @@ Web 工厂和控制台应采用工业风视觉:
- 克制的状态色和低装饰背景;
- 不复用生成内容的暖色大圆角、纸纹和宽松卡片布局。
单步执行卡片应满足:
- 只展示步骤信息和状态;
- 卡片本身不得触发运行;
- 每张卡片提供独立运行按钮;
- 点击运行按钮后弹出确认框;
- 确认框明确期次、步骤和预期覆盖范围后才能提交作业。
Web 应提供视觉计划审查入口。
自动重点区域不能由 OCR 或来源事实验证时,编辑者可以:
- 手工调整矩形;
- 手工调整出现时段和放大倍率;
- 单步重跑幻灯或渲染。
音频和视频工件应支持 HTTP Range 以便拖动预览。Web 应在桌面和窄屏可用,不以颜色单独表达状态,不依赖外部字体、分析脚本或付费生成服务。
服务入口要求如下:
@@ -294,30 +345,15 @@ Web 工厂和控制台应采用工业风视觉:
- 监听、端口、轮询和健康超时由 owning YAML 配置;
- 公网 IPv4 直连只能用于临时验收;
- 明文 HTTP 且无鉴权时必须明确风险;
- 长期公开服务应独立规划 TLS、访问控制和发布权限;
- 持续公开服务应由对应规格明确 TLS、访问控制和发布权限;
- 不能把临时公网入口默认升级为多用户生产服务。
## 7. 过程控制
### 7.1 当前实现状态与引用
### 7.1 追溯契约
- 当前已经实现 YAML 配置、TypeScript CLI、单步/区间/完整流水线、异步作业、用量费用和同源 Web
- 当前工件索引登记步骤输出,但尚未提供跨重跑内容版本兼容性校验和完整发布包
- 当前服务是主机进程和临时公网 IPv4 预览,不等价于 [平台交付](PJ2026-0207-platform-delivery.md) 目标运行面。
- 当前 Web 样式仍需按工业控制台边界收敛;生成媒体的暖色圆角主题不应继续支配控制面。
- 配置入口和校验:
- `/root/selfmedia/config/selfmedia.yaml`
- `/root/selfmedia/scripts/src/config.ts`
- CLI、作业和调度:
- `/root/selfmedia/scripts/selfmedia-cli.ts`
- `/root/selfmedia/scripts/src/jobs.ts`
- `/root/selfmedia/scripts/src/runner.ts`
- 用量和价格:`/root/selfmedia/scripts/src/llm-usage.ts``config/pricing.yaml`
- 服务和 Web`/root/selfmedia/scripts/src/server-app.ts``scripts/src/server-lifecycle.ts``web/src/`
- 作业事实:`/root/selfmedia/.state/jobs/``.state/results/``logs/`
- 后续相关源码修改应标记:
- `SPEC: PJ2026-0205 流水线平台 draft-2026-07-13-p0`
- 每个步骤、作业、尝试、用量记录和正式工件应能追溯到期次、配置引用摘要和上游结果
- 自动生成文件、纯配置、锁文件和二进制工件应由对应生成器、校验器或 owning YAML 提供稳定归属
### 7.2 原入口验收
@@ -329,6 +365,8 @@ Web 工厂和控制台应采用工业风视觉:
- 新作业具有不同标识;
- 不产生半写正式工件。
- 构建并启动同源 Web,检查桌面、窄屏、媒体 Range、章节跳转、用量、费用和全部工件。
- 验证步骤卡片本身不可点击,独立运行按钮提交前出现确认框。
- 验证视觉计划可审查,低置信重点区域可以手工调整并单步重跑。
### 7.3 安全和运行控制
@@ -2,11 +2,9 @@
## 修改历史
| 版本 | 对应 commit id | 更新日期 | 变更说明 |
| --- | --- | --- | --- |
| v0.1 | 待提交 | 2026-07-13 | 基于技术检查、真实章节时间轴、评论区来源和公网预览建立发布运营规格。 |
当前正文仍在规格治理草稿中;未定稿前不新增版本号,不为单次编辑追加 `待提交` 版本。
| 版本 | 更新日期 | 变更说明 |
| --- | --- | --- |
| v0.1 | 2026-07-13 | 定义技术检查、真实章节时间轴、评论区来源和公网预览的目标能力。 |
## 正文
@@ -19,13 +17,17 @@
| 编号 | PJ2026-0206 |
| 短名 | 发布运营 |
| 层级 | L1 方向 |
| 状态 | 已生效 |
| 实现引用版本 | draft-2026-07-13-p0 |
| 规格状态 | 已生效 |
| 需求规格模板 | [ISO/IEC/IEEE 29148 需求规格模板](../../templates/iso-iec-ieee-29148-requirements-spec-template.md) |
| 上级规格 | [PJ2026-02 智媒工厂总规格](PJ2026-02-smart-media-factory.md) |
| 规格治理索引 | [PJ2026-02 智媒工厂总规格](PJ2026-02-smart-media-factory.md) 第 7 章 |
本文采用 ISO/IEC/IEEE 29148 需求规格模板的项目裁剪版。正文只定义发布候选、时间轴、引用、人工批准、预览、归档和反馈回流的稳定需求。
本文采用 ISO/IEC/IEEE 29148 需求规格模板的项目裁剪版。
正文只定义:
- 发布候选、时间轴、引用、人工批准、预览、归档和反馈回流的预期终态;
- 对应验收契约。
## 2. 目的和范围
@@ -46,7 +48,7 @@
- 最终参考来源、媒体归因、AI 参与披露、公开成本提示和人工复核清单。
- 事实、版权、听感、视觉、优惠期限、平台规则和发布权限的人工批准或退回。
- 本地或公网 IP 预览、媒体拖动、工件访问和临时验收风险提示。
- 期次版本留存、发布状态、纠错、撤回、再生成和匿名化反馈回流的长期愿景
- 期次版本留存、发布状态、纠错、撤回、再生成和匿名化反馈回流。
### 2.3 范围外
@@ -79,7 +81,7 @@
| 外部输入 | 最终 MP4、封面、字幕、场景时间线、结构化文稿、来源清单、用量、检查报告和人工决定。 |
| 受控资源 | 发布包、评论区文本、批准状态、退回原因、版本索引、纠错记录和反馈摘要。 |
| 外部输出 | 可发布候选、时间轴、参考来源、披露、审核结论、平台预览和回流建议。 |
| 用户接口 | Web 编辑台、文件工件、媒体播放器、发布复核清单和未来平台适配器。 |
| 用户接口 | Web 编辑台、文件工件、媒体播放器、发布复核清单和受控平台适配器。 |
| 系统边界 | 发布运营负责“是否具备发布条件以及发布什么版本”;实际平台账户操作仍由授权发布者负责。 |
## 5. 内部分工与下级索引
@@ -93,7 +95,7 @@
| PJ2026-020605 | 预览交付 | 本规格 6.5 | 本地/公网预览、媒体访问和临时入口风险 | 流水线平台 | 审核者 |
| PJ2026-020606 | 版本反馈 | 本规格 6.6 | 版本、纠错、撤回、再生成和匿名反馈回流 | 发布决定、平台结果 | 全部 L1 改进 |
本章图形描述目标数据面。当前实现尚未提供持久化批准状态、兼容性发布包或反馈台账;已实现范围和目标差距见第 7.1 节
本章图形描述发布运营的预期终态数据面
### 5.1 目标数据面架构图
@@ -169,7 +171,7 @@ sequenceDiagram
end
```
时序图描述目标能力。当前只能生成候选、时间轴、来源和检查材料,尚不能持久化批准人、批准时间或发布状态
时序图中的批准人、批准时间、退回原因和发布状态都必须持久化,并与候选版本和工件哈希关联
## 6. 原子需求
@@ -203,7 +205,7 @@ sequenceDiagram
- 短标题和短释来自结构化文稿;
- 时间来自最终场景;
- 不使用按字数、目标时长或草稿场景估算的值;
- 开场大纲、总结和来源页默认不占用故事时间轴行;
- 开场大纲、来源页默认不占用故事时间轴行;
- 栏目配置可以明确要求增加其他章节。
当语速、停顿、故事顺序或场景时长改变后,渲染或检查步骤应重新生成时间轴。无法解析最终场景时间线时不得继续沿用旧评论区文本。
@@ -212,11 +214,18 @@ sequenceDiagram
| 编号 | 短名 | 主责模块 | 关联模块 |
| --- | --- | --- | --- |
| PUBLISH-L1-REQ-003 | 来源披露 | PJ2026-020603 来源披露 | [资讯源](PJ2026-0201-information-sources.md)、[编辑策划](PJ2026-0202-editorial-planning.md)、[流水线平台](PJ2026-0205-pipeline-platform.md) |
| PUBLISH-L1-REQ-003 | 来源披露 | PJ2026-020603 来源披露 | 资讯、编辑、流水线 |
关联模块为[资讯](PJ2026-0201-information-sources.md)、[编辑](PJ2026-0202-editorial-planning.md)和[流水线](PJ2026-0205-pipeline-platform.md)。
系统应在评论区时间轴之后空一行,输出“本期参考来源:”,再按故事顺序列出编号、来源名称、标题和完整链接。
来源披露只包含实际采用的来源,并保留媒体、社区、预印本和一手来源角色。引用顺序应与成片故事一致,链接不得被短链、返利链接或无法复核的聚合页替代。
来源披露应满足:
- 只包含实际采用的来源;
- 保留媒体、社区、预印本和一手来源角色;
- 引用顺序与成片故事一致;
- 链接不被短链、返利链接或无法复核的聚合页替代。
发布包披露要求如下:
@@ -251,9 +260,20 @@ sequenceDiagram
系统应允许审核者通过本地 Web 或受控公网 IP 查看期次 DAG、最终视频、音频、字幕、幻灯、来源、费用和检查结果,并支持媒体拖动与章节跳转。
临时公网预览应明确协议、鉴权和内容暴露边界。当前明文 HTTP 且无登录的公网 IPv4 入口只能用于有界验收;不应公开原始响应、LLM 请求、详细日志或未批准草稿。
临时公网预览应满足:
长期外部交付应规划 TLS、访问控制、只读发布包和最小暴露面,但这些能力在规格和授权明确前不得通过临时代理、共享口令或隐藏 URL 假装已经完成。
- 明确协议、鉴权和内容暴露边界;
- 明文 HTTP 且无登录的公网 IPv4 入口只能用于有界验收;
- 不公开原始响应、LLM 请求、详细日志或未批准草稿。
持续外部交付应提供:
- TLS
- 访问控制;
- 只读发布包;
- 最小暴露面。
这些能力在规格和授权明确前不得通过临时代理、共享口令或隐藏 URL 冒充。
### 6.6 PUBLISH-L1-REQ-006 版本、纠错与反馈回流
@@ -261,7 +281,7 @@ sequenceDiagram
| --- | --- | --- | --- |
| PUBLISH-L1-REQ-006 | 版本反馈 | PJ2026-020606 版本反馈 | 全部 L1 |
系统应长期建立发布版本、候选版本、工件哈希、批准决定和平台状态之间的关系,使发布前后纠错可以定位到具体内容和生产作业。
系统应建立发布版本、候选版本、工件哈希、批准决定和平台状态之间的关系,使发布前后纠错可以定位到具体内容和生产作业。
发生错误时应记录:
@@ -281,26 +301,10 @@ sequenceDiagram
## 7. 过程控制
### 7.1 当前实现状态与引用
### 7.1 追溯契约
- 当前已经实现候选 MP4、封面、字幕、真实故事时间轴、参考来源、费用摘要、技术检查和 Web 预览
- 当前没有持久化批准人、批准时间、退回原因、已发布状态、发布版本台账或反馈台账
- 当前输出清单不是按内容版本兼容性组合的完整发布包;第 6 章相关条款均为目标需求。
- 最终媒体和检查:
- `/root/selfmedia/scripts/src/steps/render.ts`
- `/root/selfmedia/scripts/src/steps/inspect.ts`
- 评论区和引用:`/root/selfmedia/scripts/src/comment-reference.ts`
- Web 预览:`/root/selfmedia/scripts/src/server-app.ts``web/src/`
- 当前期次工件:
- `output/episode.mp4`
- `output/cover.png`
- `output/scene-timeline.json`
- `output/inspection.json`
- `output/manifest.json`
- `script/comment-reference.txt`
- 后续相关源码修改应标记:
- `SPEC: PJ2026-0206 发布运营 draft-2026-07-13-p0`
- 每个发布候选应能追溯到成片版本、场景时间线、来源目录、检查结果、批准记录和发布版本
- 自动生成文件、纯配置、锁文件和二进制工件应由对应生成器、校验器或 owning YAML 提供稳定归属
### 7.2 原入口验收
@@ -315,7 +319,7 @@ sequenceDiagram
- 自动检查失败时不得进入人工批准;人工复核失败时不得标记可发布。
- 退回必须指定唯一主责 L1、问题描述和最小重跑范围。
- 重跑后重新生成发布包、场景时间线、评论区文本和检查结果;旧批准不自动继承。
- 当前未实现平台自动发布和账号权限时,发布动作由人工在平台官方入口完成,系统只交付发布候选和配套文本。
- 平台自动发布和账号权限未由独立规格授权时,发布动作必须由授权发布者在平台官方入口完成,系统只交付发布候选和配套文本。
### 7.4 回写边界
@@ -2,11 +2,9 @@
## 修改历史
| 版本 | 对应 commit id | 更新日期 | 变更说明 |
| --- | --- | --- | --- |
| v0.1 | 待提交 | 2026-07-13 | 建立 Web 内置 Codex、隔离凭据、受控 K8s 部署、自动交付和运行验收目标规格。 |
当前正文仍在规格治理草稿中;未定稿前不新增版本号,不为单次编辑追加 `待提交` 版本。
| 版本 | 更新日期 | 变更说明 |
| --- | --- | --- |
| v0.1 | 2026-07-13 | 定义 Web 内置 Codex、隔离凭据、受控 K8s 部署、自动交付和运行验收目标能力。 |
## 正文
@@ -19,14 +17,17 @@
| 编号 | PJ2026-0207 |
| 短名 | 平台交付 |
| 层级 | L1 方向 |
| 状态 | 已生效 |
| 实现状态 | 未实现 |
| 实现引用版本 | draft-2026-07-13-p0 |
| 规格状态 | 已生效 |
| 需求规格模板 | [ISO/IEC/IEEE 29148 需求规格模板](../../templates/iso-iec-ieee-29148-requirements-spec-template.md) |
| 上级规格 | [PJ2026-02 智媒工厂总规格](PJ2026-02-smart-media-factory.md) |
| 规格治理索引 | [PJ2026-02 智媒工厂总规格](PJ2026-02-smart-media-factory.md) 第 7 章 |
本文采用 ISO/IEC/IEEE 29148 需求规格模板的项目裁剪版。正文定义智媒工厂从本地主机进程演进为可受控交付平台时的稳定目标,不把目标状态写成当前已实现事实。
本文采用 ISO/IEC/IEEE 29148 需求规格模板的项目裁剪版。
正文边界如下:
- 只定义受控交付平台的预期终态、目标能力和验收契约;
- 不记录当前运行方式或实现进度。
## 2. 目的和范围
@@ -39,18 +40,18 @@
- 在容器中提供随镜像交付的源码、CLI 和受限工具;
- 通过 YAML-first 配置、GitOps 自动链和公网 IP 原入口持续交付。
### 2.2 当前状
### 2.2 预期终
当前 `/root/selfmedia` 已具备本地主机上的 CLI、Web 服务、期次作业和公网 IPv4 临时预览,但尚未实现本规格要求的
平台交付终态必须具备
- Web 内置 Codex 终端;
- 包含版本化源码和 CLI 的后端容器运行面
- PVC 会话持久化、重启自动恢复和 Codex 会话续接;
- YAML-first Secret `sourceRef``~/.env/TOKEN`隔离投影;
- 受控 `~/.codex/*.pika` 源路径到运行时 Codex home 的模型配置与 API key 投影;
- 可从智媒工厂 Web 直接访问的内置 Codex 终端;
- 包含版本化源码、TypeScript CLI 和运行依赖的后端容器;
- 通过 PVC 持久化的 Codex session、supervisor 恢复元数据和重启自动续接;
- YAML-first Secret `sourceRef``~/.env/TOKEN`独立只读投影;
- 受控 `~/.codex/*.pika` 源路径到运行时 Codex home 的独立模型配置与 API key 投影;
- 无 ServiceAccount token、无 kubeconfig、无 Kubernetes 或主机权限的终端沙箱;
- GitHubmirror、PaC、Tekton、GitOps 和 Argo 的自动交付链;
- 由公网 IP 原入口执行的部署后运行验收。
- GitHubmirror、PaC、Tekton、GitOps 和 Argo 组成的自动交付链;
- 由 owning YAML 选定公网 IPv4 原入口执行的部署后运行验收。
### 2.3 范围内
@@ -107,12 +108,12 @@
| --- | --- | --- | --- | --- | --- |
| PJ2026-020701 | 内置终端 | 本规格 6.1 | Web 终端、Codex 进程、会话连接、重连和用户状态 | Web、后端容器、会话 PVC | 编辑调试 |
| PJ2026-020702 | Agent 工具 | 本规格 6.2 | 版本化源码、TypeScript CLI、受限 Skill/API 和工件访问 | 构建产物、流水线平台 | Codex Agent |
| PJ2026-020703 | 隔离凭据 | 本规格 6.3 | 后端 Token、Codex 模型配置的独立 `sourceRef`、文件权限、脱敏和负面边界 | owning YAML、外部 Secret | 内置终端、Agent 工具 |
| PJ2026-020703 | 隔离凭据 | 本规格 6.3 | 两类凭据的独立投影、权限、脱敏和负面边界 | owning YAML、外部 Secret | 内置终端、Agent 工具 |
| PJ2026-020704 | 受控部署 | 本规格 6.4 | YAML-first K8s 工作负载、资源、健康和公网 IP | 镜像摘要、配置与 Secret 引用 | 自动交付、运行验收 |
| PJ2026-020705 | 自动交付 | 本规格 6.5 | GitHub、mirror、PaC、Tekton、GitOps 和 Argo 链 | 源码与 owning YAML | 受控部署 |
| PJ2026-020706 | 运行验收 | 本规格 6.6 | 公网原入口、版本、终端、恢复、隔离和自动链证据 | 全部平台交付模块 | 发布运营、平台维护者 |
本章图形全部描述目标数据面。当前不存在可与这些目标等价的正式 Kubernetes 运行面,当前差距见第 2.2 和第 7.1 节
本章图形全部描述平台交付的预期终态数据面
### 5.1 目标数据面架构图
@@ -158,7 +159,7 @@ flowchart LR
```mermaid
flowchart TD
Commit[GitHub 交] --> Sync[mirror 同步]
SourceVersion[GitHub 交付源版本] --> Sync[mirror 同步]
Sync --> Build[PaC 触发 Tekton]
Build --> Test[源码、CLI 与镜像验证]
Test --> Digest[不可变镜像摘要]
@@ -199,7 +200,7 @@ sequenceDiagram
participant K as Kubernetes
participant V as 运行验收
G->>M: 推送受审查提交
M->>T: 触发 commit-pinned PipelineRun
M->>T: 触发交付源版本固定的 PipelineRun
T->>T: 校验源码、CLI、镜像和配置
T->>R: 发布不可变镜像摘要
T->>O: 提交摘要固定的目标配置
@@ -273,7 +274,13 @@ sequenceDiagram
- `~/.codex/sessions`
- supervisor 恢复元数据。
会话 PVC 不承担源码工作区持久化。需要跨重启保存的源码变更必须进入受控变更流;未来如需持久化源码工作区,应另行定义数据生命周期、版本冲突和回收要求。
会话 PVC 不承担源码工作区持久化。
需要跨重启保存的源码变更必须进入受控变更流。未来如需持久化源码工作区,应另行定义:
- 数据生命周期;
- 版本冲突处理;
- 回收要求。
后端容器或 Pod 重启后,平台应自动发现并续接可恢复会话。恢复失败时输出明确原因,不以新会话冒充恢复成功。
@@ -283,7 +290,13 @@ sequenceDiagram
| --- | --- | --- | --- |
| DELIVERY-L1-REQ-002 | Agent 工具 | PJ2026-020702 Agent 工具 | [流水线平台](PJ2026-0205-pipeline-platform.md)、[发布运营](PJ2026-0206-publishing-operations.md) |
后端容器镜像应包含与部署 commit 一致的智媒工厂源码、TypeScript CLI、运行依赖和必要开源媒体工具,使 Codex 能在容器内执行正式单步、查看作业和复核工件。
后端容器镜像应包含与部署交付源版本一致的智媒工厂源码和工具链:
- TypeScript CLI
- 运行依赖;
- 必要开源媒体工具。
Codex 应能在容器内执行正式单步、查看作业和复核工件。
Agent 工具面只暴露 owning YAML 允许的:
@@ -349,9 +362,20 @@ owning YAML 应定义:
代码或手工命令中不得保留第二套部署事实。
Kubernetes 工作负载应使用非 root 用户、只读根文件系统或等价最小写面、明确 CPU/内存/临时存储限制,并把可写内容限制在声明的会话 PVC 和必要临时目录。
Kubernetes 工作负载应
公网入口应通过 YAML 声明的 IPv4 和端口暴露 Web、同源 API 与终端代理。验收使用该公网 IP 原入口,不以 Pod IP、端口转发、SSH、临时 NodePort 或集群内部地址替代。
- 使用非 root 用户;
- 使用只读根文件系统或等价最小写面;
- 明确 CPU、内存和临时存储限制;
- 把可写内容限制在声明的会话 PVC 和必要临时目录。
公网入口应通过 YAML 声明的 IPv4 和端口暴露:
- Web
- 同源 API
- 终端代理。
验收使用该公网 IP 原入口,不以 Pod IP、端口转发、SSH、临时 NodePort 或集群内部地址替代。
### 6.5 DELIVERY-L1-REQ-005 GitOps 自动交付链
@@ -362,7 +386,7 @@ Kubernetes 工作负载应使用非 root 用户、只读根文件系统或等价
平台应建立以下自动交付链:
- GitHub 提交进入受控 mirror
- Pipeline as Code 触发 commit-pinned Tekton 流水线;
- Pipeline as Code 触发交付源版本固定的 Tekton 流水线;
- Tekton 验证源码、CLI、配置和镜像;
- 验证通过后发布不可变镜像摘要;
- 更新 GitOps 目标;
@@ -374,13 +398,22 @@ Kubernetes 工作负载应使用非 root 用户、只读根文件系统或等价
- Tekton 负责构建、检查和产物发布,不直接修改生产 Deployment;
- Argo 只消费通过审核和验证的 GitOps 目标 revision。
任何链路失败都应保留 commit、流水线、镜像摘要、GitOps revision、Argo 协调和运行验收之间的追溯关系。自动重试不能跳过失败门禁或回退到手工复制。
任何链路失败都应保留以下对象之间的追溯关系:
- 交付源版本;
- 流水线;
- 镜像摘要;
- GitOps revision
- Argo 协调;
- 运行验收。
自动重试不能跳过失败门禁或回退到手工复制。
### 6.6 DELIVERY-L1-REQ-006 公网原入口运行验收
| 编号 | 短名 | 主责模块 | 关联模块 |
| --- | --- | --- | --- |
| DELIVERY-L1-REQ-006 | 运行验收 | PJ2026-020706 运行验收 | PJ2026-020701 内置终端、PJ2026-020702 Agent 工具、PJ2026-020703 隔离凭据、PJ2026-020704 受控部署、PJ2026-020705 自动交付 |
| DELIVERY-L1-REQ-006 | 运行验收 | PJ2026-020706 运行验收 | 本规格 6.1 至 6.5 |
每次自动交付完成后,平台应从 YAML 选中的公网 IPv4 原入口验证健康、Web、同源 API、内置终端、Codex 启动、正式 CLI、工件读取和版本可见性。
@@ -399,43 +432,26 @@ Kubernetes 工作负载应使用非 root 用户、只读根文件系统或等价
- `~/.env/TOKEN` 只报告 `sourceRef``targetKey`、presence 和 fingerprint
- 运行时 Codex home 的 pika 配置只报告 `sourceRef``targetKey`、presence 和 fingerprint
- 两类凭据值都不出现在日志、规格、API、终端回显或工件;
- 运行 commit、镜像摘要、GitOps revision 和公网验收结果可以关联。
- 运行交付源版本、镜像摘要、GitOps revision 和公网验收结果可以关联。
## 7. 过程控制
### 7.1 当前实现状态与目标差距
### 7.1 追溯契约
- 当前 `/root/selfmedia` 以主机 Bun 进程运行 CLI 和 Web;这只能作为功能样片和临时公网验收基线
- 当前不存在本规格定义的 Kubernetes 应用、Web Codex 终端、会话 PVC、两类隔离凭据投影和 GitOps 自动交付链
- 本规格作为目标约束已经生效,但实现状态仍为“未实现”。
- 所有第 6 章内容都是目标需求,不得用于宣称已经上线。
- 后续新增或修改的相关源码文件应标记:
- `SPEC: PJ2026-0207 平台交付 draft-2026-07-13-p0`
- 纯 YAML、生成文件、锁文件和二进制镜像可以不加文件头:
- owning YAML、渲染器、容器构建入口和交付流水线必须能追溯到本规格。
- 每个部署版本应能追溯到交付源版本、镜像摘要、GitOps 目标、运行版本和验收结论
- 纯 YAML、生成文件、锁文件和二进制镜像应由 owning YAML、渲染器、容器构建入口或交付流水线提供稳定归属
### 7.2 实施顺序
- 先交付不含 Kubernetes 权限的后端容器、源码、CLI 和只读健康入口。
- 再交付会话 PVC、Web 内置终端和 Codex 新建/恢复。
- 随后接入两类 YAML-first 凭据 `sourceRef`
- 后端 API `~/.env/TOKEN`
- Codex `~/.codex/*.pika` 源到运行时 Codex home 的配置与认证文件。
- 两类凭据隔离后,再接入权限负面检查和受限 Skill/API。
- 完成 YAML-first Kubernetes、公网 IPv4 原入口和运行验收后,才接入自动交付链。
- 最后用 GitHub、mirror、PaC、Tekton、GitOps 和 Argo 全链替换手工发布路径。
### 7.3 安全门禁
### 7.2 安全门禁
- 发现 ServiceAccount token、kubeconfig、Kubernetes API 可达、hostPath、容器套接字或特权能力时,运行验收必须失败。
- 任一后端或 Codex 凭据值出现在日志、规格、终端、API、工件或镜像层时,必须停止交付并轮换受影响凭据。
- 终端不能直接调用部署控制面;需要部署动作时只能通过受控自动交付入口产生可审查变更。
- 受限 Skill/API 策略变化先更新 owning YAML;扩大集群或主机权限必须先更新本规格并重新评审边界。
### 7.4 原入口验收
### 7.3 原入口验收
- 使用公网 IPv4 打开 Web 并创建 Codex 终端。
- 验证容器内源码 commit、CLI 版本和已部署镜像摘要一致。
- 验证容器内交付源版本、CLI 版本和已部署镜像摘要一致。
- 通过终端执行配置计划、步骤列表或等价只读命令。
- 重启后端 Pod,验证同一 sessionId 和终端上下文依靠 PVC 中的 Codex sessions 与 supervisor 元数据恢复。
- 检查 `~/.env/TOKEN``sourceRef``targetKey`、presence、文件权限和 fingerprint。
@@ -443,7 +459,7 @@ Kubernetes 工作负载应使用非 root 用户、只读根文件系统或等价
- 不读取、保存或输出任一凭据值。
- 运行权限负面检查和 GitHub 到 Argo 的交付追溯检查。
### 7.5 回写边界
### 7.4 回写边界
- 内容步骤、作业、日志或工件契约问题回写 [流水线平台](PJ2026-0205-pipeline-platform.md)。
- 发布包、人工批准和平台发布材料问题回写 [发布运营](PJ2026-0206-publishing-operations.md)。
@@ -2,7 +2,12 @@
## 来源和定位
本模板用于 UniDesk `project-management/` 下的 L0、L1、L2 需求规格文档。模板基线采用 ISO/IEC/IEEE 29148:2018 的需求工程分层思路,并结合 HWLAB 项目管理约束做轻量裁剪。
本模板用于 UniDesk `project-management/` 下的 L0、L1、L2 需求规格文档。
模板基线包括:
- ISO/IEC/IEEE 29148:2018 的需求工程分层思路;
- HWLAB 项目管理约束的轻量裁剪。
参考来源:
@@ -13,11 +18,37 @@
使用原则:
- 业务需求规格用于使命和商业目标,利益相关方需求规格用于用户和运营方诉求,系统需求规格用于跨硬件、软件、人员和流程的系统级要求,软件需求规格用于软件能力要求。运行流程和一次性执行过程不进入 1-6 章需求正文;需要挂阶段 issue、内测、灰度或反馈分流时,放入第 7 章过程控制,稳定概念优先沉淀为术语表。
- HWLAB 的 L0、L1、L2 规格共用同一骨架,但粒度不同:L0 写使命、边界和全局需求;L1 写内部模块职责和验收边界;L2 写具体课题的交付物、阻塞项和验证计划。
- 规格正文的 1-6 章只写稳定需求、系统边界、外部输入输出和职责;内部治理、阶段报告、执行流水、长日志、拉取请求过程和一次性运行材料不进入需求章节。第 7 章过程控制只做阶段 issue、反馈分流和敏感信息边界的索引,不承载问卷正文、参与者资料、原始反馈或长证据。
- 每条原子需求使用独立小节。信息表使用横向表格且只放短元数据,推荐包含编号、短名、主责模块和关联模块;主责模块填写下一层主责单元,L0 写 L1,L1 写 L2,L2 写 L3,不重复当前规格本级;需求定义、职责划分、意图、范围和边界写在正文中,避免表格厚而正文薄。
- 不要为了凑全局需求条目自动添加证据收集、硬编码诊断、硬编码建议、任务状态收集、结果包或结果收集类次要需求;除非用户明确授意,这些内容不进入 OA 规格
- 需求规格类型如下:
- 业务需求规格用于使命和商业目标;
- 利益相关方需求规格用于用户和运营方诉求;
- 系统需求规格用于跨硬件、软件、人员和流程的系统级要求;
- 软件需求规格用于软件能力要求
- L0、L1、L2 规格共用同一骨架,但粒度不同:
- L0 写使命、边界、预期终态和全局需求;
- L1 写内部模块职责和验收边界;
- L2 写具体课题的目标交付物和验证契约。
- SPEC 全文只写预期终态:
- 目标能力;
- 稳定边界;
- 目标架构、数据流和接口;
- 原子需求;
- 验收契约和稳定过程控制。
- SPEC 不写执行态:
- 当前实现状态或能力基线;
- 已完成、未完成、当前阻塞或阶段差距;
- 源码路径、commit、PR、作业、日志、截图、样片或一次性证据。
- 执行态进入阶段报告、任务报告、偏离记录或 GitHub issue,并从这些载体引用 SPEC。
- 每条原子需求使用独立小节:
- 信息表使用横向表格且只放编号、短名和主责模块等短元数据;
- 主责模块填写下一层主责单元,L0 写 L1,L1 写 L2L2 写 L3
- 不重复当前规格本级;
- 关联模块、需求定义、职责划分、意图、范围和边界写在正文中。
- 不要为了凑全局需求条目自动添加次要需求:
- 证据收集;
- 硬编码诊断或建议;
- 任务状态收集;
- 结果包或结果收集。
- 除非用户明确授意,上述次要内容不进入 OA 规格。
## 文档骨架
@@ -26,11 +57,11 @@
## 修改历史
| 版本 | 对应提交标识 | 更新日期 | 变更说明 |
| --- | --- | --- | --- |
| v0.1 | 待提交 | <年-月-日> | 创建需求规格;如来自历史议题,只在这里写迁移来源 <组织>/<仓库>#<编号>。 |
| 版本 | 更新日期 | 变更说明 |
| --- | --- | --- |
| v0.1 | <年-月-日> | 创建预期终态需求规格;如来自历史议题,只在这里写迁移来源 <组织>/<仓库>#<编号>。 |
未定稿前不新增版本号,不为单次编辑追加 `待提交` 版本;只有用户明确确认可以定稿时,才更新修改历史
修改历史只记录规格语义变更,不记录实现进度、阶段基线或一次性证据
## 正文
@@ -43,12 +74,18 @@
| 编号 | <项目编号> |
| 短名 | <短名> |
| 层级 | L0、L1 或 L2 |
| 状态 | 已生效、已废弃或未生效 |
| 规格状态 | 已生效、已废弃或未生效;只表示规格生命周期 |
| 需求规格模板 | [ISO/IEC/IEEE 29148 需求规格模板](<相对路径>) |
| 上级规格 | <相对路径链接;L0 可留空> |
| 规格治理索引 | <相对路径链接> |
本文采用 ISO/IEC/IEEE 29148 需求规格模板的项目裁剪版:正文只保留稳定使命、范围、术语、系统边界、内部模块或课题分工、原子需求和必要过程控制。编号、层级职责、回写、偏离判定和交叉引用治理由规格治理文档承载
本文采用 ISO/IEC/IEEE 29148 需求规格模板的项目裁剪版。
正文边界如下:
- 只定义预期终态、稳定使命、范围、术语、系统边界、内部分工、原子需求和验收契约;
- 不记录实现进度、阶段基线或执行证据;
- 编号、层级职责、回写、偏离判定和交叉引用治理由规格治理文档承载。
## 2. 目的和范围
@@ -101,9 +138,9 @@
## 7. 过程控制
本章只索引跨模块阶段验证、内测、灰度或用户反馈分流 issue;不新增 L1,不替代阶段计划,不把问卷、参与者资料、原始反馈、长证据或执行流水写入规格正文
本章只定义长期稳定的实现引用、验收、配置、安全、偏离和回写规则
| 阶段 issue | 定位 | 关联模块 | 回写口径 |
| --- | --- | --- | --- |
| [#<编号>](https://github.com/<组织>/<仓库>/issues/<编号>) <短名> | <阶段验证或反馈分流定位> | <关联 L1/L2> | <脱敏、分流和回写规则> |
- 当前状态、阶段验证、内测、灰度和用户反馈进入阶段报告或执行 issue;
- 问卷、参与者资料、原始反馈、长证据和执行流水不进入 SPEC;
- 执行载体应引用本 SPEC 编号和适用的规格版本。
```
+202 -1
View File
@@ -1,6 +1,13 @@
import { readFileSync, rmSync } from "node:fs";
import { describe, expect, test } from "bun:test";
import { parseAgentRunClientConfigYaml, resolveAgentRunAuth, runAgentRunCommand, stripAgentRunResourceWrapperArgs } from "./agentrun";
import {
parseAgentRunClientConfigYaml,
parseResourceOptions,
renderMutationSummary,
resolveAgentRunAuth,
runAgentRunCommand,
stripAgentRunResourceWrapperArgs,
} from "./agentrun";
import { resolveAgentRunLaneTarget } from "./agentrun-lanes";
import { agentRunToolCredentialRefs } from "./agentrun/config";
import { autoDispatchTargetTask, targetTaskDispatchDryRunPlan } from "./agentrun/rest-bridge";
@@ -318,6 +325,200 @@ describe("Artificer Target one-command dispatch", () => {
rmSync(tempConfigPath, { force: true });
}
});
test("keeps Target live human, JSON, and YAML output bounded while retaining dispatch identities", () => {
const outputConfig = Bun.YAML.parse(
readFileSync(new URL("../../config/unidesk-cli.yaml", import.meta.url), "utf8"),
) as { output?: { maxStdoutBytes?: number } };
const maxStdoutBytes = outputConfig.output?.maxStdoutBytes;
if (typeof maxStdoutBytes !== "number") throw new Error("expected YAML-first output.maxStdoutBytes");
const hugePrompt = "PROMPT_SHOULD_NOT_BE_RENDERED_".repeat(500);
expect(Buffer.byteLength(hugePrompt, "utf8")).toBeGreaterThan(maxStdoutBytes);
const identity = {
taskId: "qt_compact_target",
taskState: "running",
attemptId: "qat_compact_target",
runId: "run_compact_target",
commandId: "cmd_compact_target",
runnerJobId: "rjob_compact_target",
sessionId: "sess_compact_target",
valuesPrinted: false,
};
const autoDispatch = {
enabled: true,
decision: "dispatched",
mutation: true,
duplicateDispatchPrevented: false,
identity,
valuesPrinted: false,
};
const raw = {
ok: true,
action: "queue-submit-auto-dispatch",
data: {
...identity,
attemptState: "running",
runState: "running",
commandState: "running",
runnerState: "claimed",
sessionState: "active",
mutation: true,
autoDispatch,
task: {
id: identity.taskId,
state: identity.taskState,
prompt: hugePrompt,
payload: { prompt: hugePrompt },
latestAttempt: {
attemptId: identity.attemptId,
state: "running",
runnerJobId: identity.runnerJobId,
runnerState: "claimed",
sessionId: identity.sessionId,
},
},
run: {
id: identity.runId,
state: "running",
sessionRef: { sessionId: identity.sessionId, state: "active" },
},
command: { id: identity.commandId, state: "running", payload: { prompt: hugePrompt } },
latestAttempt: {
attemptId: identity.attemptId,
state: "running",
runId: identity.runId,
commandId: identity.commandId,
runnerJobId: identity.runnerJobId,
runnerState: "claimed",
sessionId: identity.sessionId,
},
session: { id: identity.sessionId, state: "active", prompt: hugePrompt },
valuesPrinted: false,
},
targetTask: {
mdtodoId: "R6.4",
target: "NC01",
targetWorkspace: "/root/.worktrees/unidesk/artificer-compact-output",
targetRoute: "NC01:/root/.worktrees/unidesk/artificer-compact-output",
projectId: "pikasTech/unidesk",
repository: "pikasTech/unidesk",
ref: "fix/artificer-compact-output",
verifiedCommit: "a".repeat(40),
refRelation: "branch-and-head-match-remote-ref",
workspace: {
branch: "fix/artificer-compact-output",
headCommit: "a".repeat(40),
clean: true,
},
prompt: hugePrompt,
},
aipodBinding: {
aipod: "Artificer",
node: "NC01",
lane: "nc01-v02",
namespace: "agentrun-v01",
projectId: "pikasTech/unidesk",
providerId: "NC01",
backendProfile: "sub2api",
workspaceRef: { kind: "opaque", path: ".", valid: true },
resourceBundleRef: {
kind: "gitbundle",
repoUrl: "https://credential-must-be-redacted@github.com/pikasTech/unidesk.git",
ref: "fix/artificer-compact-output",
inheritedFromAipod: true,
credentialRefPresent: true,
},
session: { sessionId: identity.sessionId, identitySource: "task" },
providerCredentials: [{
profile: "sub2api",
secretRef: { namespace: "agentrun-v01", name: "agentrun-provider", keys: ["auth.json"] },
bindingPresent: true,
value: hugePrompt,
}],
toolCredentials: [{
tool: "github",
purpose: "pull-request",
secretRef: { namespace: "agentrun-v01", name: "agentrun-github", keys: ["GH_TOKEN"] },
bindingPresent: true,
value: hugePrompt,
}],
prompt: hugePrompt,
},
autoDispatch,
valuesPrinted: false,
};
expect(Buffer.byteLength(JSON.stringify(raw), "utf8")).toBeGreaterThan(maxStdoutBytes);
const variants = [
{ name: "human", args: [] },
{ name: "json", args: ["-o", "json"] },
{ name: "yaml", args: ["-o", "yaml"] },
];
for (const variant of variants) {
const rendered = renderMutationSummary(
"agentrun queue submit",
raw,
parseResourceOptions(variant.args),
"Queue task submitted",
);
const text = renderedTextOf(rendered);
expect(Buffer.byteLength(text, "utf8"), variant.name).toBeLessThan(maxStdoutBytes);
expect(text).not.toContain("PROMPT_SHOULD_NOT_BE_RENDERED_");
expect(text).not.toContain("credential-must-be-redacted");
for (const value of Object.values(identity).filter((item): item is string => typeof item === "string")) {
expect(text, `${variant.name}:${value}`).toContain(value);
}
expect(text).toContain("R6.4");
expect(text).toContain("dispatched");
}
const jsonText = renderedTextOf(renderMutationSummary(
"agentrun queue submit",
raw,
parseResourceOptions(["-o", "json"]),
"Queue task submitted",
));
const json = JSON.parse(jsonText) as Record<string, unknown>;
const jsonData = json.data as Record<string, unknown>;
const jsonNext = json.next as Record<string, unknown>;
const jsonCredentials = (json.aipodBinding as Record<string, unknown>).credentialRefs as Record<string, unknown>;
expect(jsonData).toMatchObject({
taskId: identity.taskId,
taskState: "running",
attemptId: identity.attemptId,
attemptState: "running",
runId: identity.runId,
runState: "running",
commandId: identity.commandId,
commandState: "running",
runnerJobId: identity.runnerJobId,
runnerState: "claimed",
sessionId: identity.sessionId,
sessionState: "active",
});
expect((json.autoDispatch as Record<string, unknown>).decision).toBe("dispatched");
expect((json.targetTask as Record<string, unknown>).mdtodoId).toBe("R6.4");
expect(jsonCredentials).toMatchObject({ totalCount: 2, allPresent: true });
expect((jsonCredentials.provider as Record<string, unknown>).presentCount).toBe(1);
expect((jsonCredentials.tool as Record<string, unknown>).presentCount).toBe(1);
expect(jsonNext.result).toBe(
`bun scripts/cli.ts agentrun result run/${identity.runId} --command ${identity.commandId} --target NC01 --lane nc01-v02`,
);
expect(jsonNext.logs).toBe(
`bun scripts/cli.ts agentrun logs session/${identity.sessionId} --tail 120 --target NC01 --lane nc01-v02`,
);
const yamlText = renderedTextOf(renderMutationSummary(
"agentrun queue submit",
raw,
parseResourceOptions(["-o", "yaml"]),
"Queue task submitted",
));
const yaml = Bun.YAML.parse(yamlText) as Record<string, unknown>;
expect(yaml.data).toMatchObject(json.data as object);
expect(yaml.next).toMatchObject(json.next as object);
});
});
describe("AgentRun default transport contract", () => {
+32 -11
View File
@@ -39,6 +39,7 @@ import { status } from "./control-plane";
import { arrayRecords, displayValue, isRecord, nextPagedResourceCommand, parseTaskManifest, pickCompact, relativeAge, renderFailureLines, renderResourceNextLines, renderTable, resourceName, shortId, stringOrDash, truncateMultiline, truncateOneLine } from "./options";
import { activeAgentRunRestTarget, agentRunRestRequest, runAgentRunRestCommand } from "./rest-bridge";
import { renderSessionSendResult } from "./session-send-render";
import { compactTargetTaskMutationPayload } from "./target-task-output";
import { record, stringOrNull } from "./utils";
export function resolveAgentRunCancelPolicyTarget(config: UniDeskConfig | null, options: AgentRunResourceOptions): { configPath: string; spec: AgentRunLaneSpec; source: "selected-lane" | "default-lane" } | null {
@@ -479,7 +480,10 @@ export function renderResultSummary(command: string, raw: Record<string, unknown
export function renderMutationSummary(command: string, raw: Record<string, unknown>, options: AgentRunResourceOptions, headline: string, overrideNextLines?: string[]): RenderedCliResult {
if (options.raw) return renderMachine(command, raw, "json", raw.ok !== false);
if (options.output === "json" || options.output === "yaml") return renderMachine(command, raw, options.output, raw.ok !== false);
const targetTaskPayload = compactTargetTaskMutationPayload(raw);
if (options.output === "json" || options.output === "yaml") {
return renderMachine(command, targetTaskPayload ?? raw, options.output, raw.ok !== false);
}
const data = record(innerData(raw));
const id = stringOrNull(data.id) ?? stringOrNull(data.taskId) ?? stringOrNull(data.sessionId);
const lines = [
@@ -496,12 +500,13 @@ export function renderMutationSummary(command: string, raw: Record<string, unkno
if (decision !== null) lines.push(`Decision: ${decision}`);
if (internalCommandType !== null) lines.push(`InternalCommandType: ${internalCommandType}`);
lines.push(...renderCancelLifecycleMutationLines(record(data.cancelLifecycle ?? raw.cancelLifecycle)));
lines.push(...renderTargetTaskMutationLines(record(data.targetTask ?? raw.targetTask)));
lines.push(...renderAipodBindingMutationLines(record(data.aipodBinding ?? raw.aipodBinding)));
lines.push(...renderAutoDispatchMutationLines(record(data.autoDispatch ?? raw.autoDispatch)));
lines.push(...renderTargetTaskMutationLines(record(targetTaskPayload?.targetTask ?? data.targetTask ?? raw.targetTask)));
lines.push(...renderAipodBindingMutationLines(record(targetTaskPayload?.aipodBinding ?? data.aipodBinding ?? raw.aipodBinding)));
lines.push(...renderAutoDispatchMutationLines(record(targetTaskPayload?.autoDispatch ?? data.autoDispatch ?? raw.autoDispatch)));
lines.push(...renderDispatchPlanMutationLines(record(data.dispatchPlan ?? raw.dispatchPlan)));
const next = record(raw.next ?? data.next);
const nextLines = (overrideNextLines ?? Object.values(next).map(String)).filter((line) => line.length > 0).slice(0, 5);
const next = record(targetTaskPayload?.next ?? raw.next ?? data.next);
const nextLimit = targetTaskPayload === null ? 5 : 8;
const nextLines = (overrideNextLines ?? Object.values(next).map(String)).filter((line) => line.length > 0).slice(0, nextLimit);
if (nextLines.length > 0) lines.push("", "Next:", ...nextLines.map((line) => ` ${line}`));
return renderedCliResult(raw.ok !== false, command, lines.join("\n"));
}
@@ -525,8 +530,11 @@ export function renderAipodBindingMutationLines(binding: Record<string, unknown>
const workspaceRef = record(binding.workspaceRef);
const resourceBundleRef = record(binding.resourceBundleRef);
const session = record(binding.session);
const providerCredentials = arrayRecords(binding.providerCredentials);
const toolCredentials = arrayRecords(binding.toolCredentials);
const credentialRefs = record(binding.credentialRefs);
const providerSummary = record(credentialRefs.provider);
const toolSummary = record(credentialRefs.tool);
const providerCredentials = arrayRecords(binding.providerCredentials ?? providerSummary.items);
const toolCredentials = arrayRecords(binding.toolCredentials ?? toolSummary.items);
const lines = [
"",
"AipodBinding:",
@@ -535,6 +543,16 @@ export function renderAipodBindingMutationLines(binding: Record<string, unknown>
` ResourceBundle: ${displayValue(resourceBundleRef.repoUrl)} ref=${displayValue(resourceBundleRef.ref)} inherited=${displayValue(resourceBundleRef.inheritedFromAipod)}`,
` Session: ${displayValue(session.sessionId)} source=${displayValue(session.identitySource)}`,
];
if (Object.keys(credentialRefs).length > 0) {
const providerPresence = `${displayValue(providerSummary.presentCount)}/${displayValue(providerSummary.count)}`;
const toolPresence = `${displayValue(toolSummary.presentCount)}/${displayValue(toolSummary.count)}`;
lines.push([
` SecretRefs: total=${displayValue(credentialRefs.totalCount)}`,
`allPresent=${displayValue(credentialRefs.allPresent)}`,
`provider=${providerPresence}`,
`tool=${toolPresence}`,
].join(" "));
}
for (const credential of providerCredentials.slice(0, 4)) {
const secretRef = record(credential.secretRef);
lines.push(` ProviderSecretRef: ${displayValue(secretRef.namespace)}/${displayValue(secretRef.name)} keys=${displayValue(secretRef.keys)} present=${displayValue(credential.bindingPresent)}`);
@@ -553,9 +571,12 @@ export function renderAutoDispatchMutationLines(autoDispatch: Record<string, unk
"",
"AutoDispatch:",
` Decision: ${displayValue(autoDispatch.decision)} mutation=${displayValue(autoDispatch.mutation)} duplicatePrevented=${displayValue(autoDispatch.duplicateDispatchPrevented)}`,
` Task: ${displayValue(identity.taskId)} state=${displayValue(identity.taskState)} attempt=${displayValue(identity.attemptId)}`,
` Run: ${displayValue(identity.runId)} command=${displayValue(identity.commandId)} runner=${displayValue(identity.runnerJobId)}`,
` Session: ${displayValue(identity.sessionId)}`,
` Task: ${displayValue(identity.taskId)} state=${displayValue(identity.taskState)}`,
` Attempt: ${displayValue(identity.attemptId)} state=${displayValue(identity.attemptState)}`,
` Run: ${displayValue(identity.runId)} state=${displayValue(identity.runState)}`,
` Command: ${displayValue(identity.commandId)} state=${displayValue(identity.commandState)}`,
` Runner: ${displayValue(identity.runnerJobId)} state=${displayValue(identity.runnerState)}`,
` Session: ${displayValue(identity.sessionId)} state=${displayValue(identity.sessionState)}`,
];
}
+271
View File
@@ -0,0 +1,271 @@
type TargetDispatchDecision = "dispatched" | "idempotent-replay" | "concurrent-replay";
export function compactTargetTaskMutationPayload(raw: Record<string, unknown>): Record<string, unknown> | null {
const data = asRecord(innerData(raw));
const targetTask = firstRecord(data.targetTask, raw.targetTask);
const aipodBinding = firstRecord(data.aipodBinding, raw.aipodBinding);
const autoDispatch = firstRecord(data.autoDispatch, raw.autoDispatch);
const decision = boundedText(autoDispatch.decision, 80);
if (Object.keys(targetTask).length === 0 || !isTargetDispatchDecision(decision)) return null;
const identity = compactTargetTaskIdentity(data, autoDispatch);
const targetSummary = compactTargetTaskSummary(targetTask);
const aipodSummary = compactTargetAipodBinding(aipodBinding);
const autoDispatchSummary = {
enabled: autoDispatch.enabled === true,
decision,
mutation: booleanOrNull(autoDispatch.mutation),
duplicateDispatchPrevented: booleanOrNull(autoDispatch.duplicateDispatchPrevented),
identity,
valuesPrinted: false,
};
const next = targetTaskIdentityNext(identity, targetSummary, aipodSummary);
return {
ok: raw.ok !== false,
action: boundedText(raw.action, 120) ?? "queue-submit-auto-dispatch",
data: {
...identity,
state: identity.taskState,
mutation: booleanOrNull(data.mutation) ?? autoDispatchSummary.mutation,
autoDispatch: autoDispatchSummary,
valuesPrinted: false,
},
targetTask: targetSummary,
aipodBinding: aipodSummary,
autoDispatch: autoDispatchSummary,
next,
valuesPrinted: false,
};
}
function compactTargetTaskIdentity(
data: Record<string, unknown>,
autoDispatch: Record<string, unknown>,
): Record<string, unknown> {
const declared = asRecord(autoDispatch.identity);
const task = asRecord(data.task);
const attempt = firstRecord(data.latestAttempt, task.latestAttempt);
const run = asRecord(data.run);
const command = asRecord(data.command);
const runner = firstRecord(data.runner, data.runnerJob);
const session = firstRecord(data.session, run.sessionRef, task.sessionRef);
return {
taskId: firstText(200, declared.taskId, data.taskId, task.id),
taskState: firstText(80, declared.taskState, data.taskState, task.state, task.status),
attemptId: firstText(200, declared.attemptId, data.attemptId, attempt.attemptId, attempt.id),
attemptState: firstText(80, data.attemptState, attempt.state, attempt.status),
runId: firstText(200, declared.runId, data.runId, run.id),
runState: firstText(80, data.runState, run.state, run.status),
commandId: firstText(200, declared.commandId, data.commandId, command.id),
commandState: firstText(80, data.commandState, command.state, command.status),
runnerJobId: firstText(200, declared.runnerJobId, data.runnerJobId, attempt.runnerJobId, runner.id),
runnerState: firstText(80, data.runnerState, attempt.runnerState, runner.state, runner.status),
sessionId: firstText(240, declared.sessionId, data.sessionId, attempt.sessionId, session.sessionId, session.id),
sessionState: firstText(80, data.sessionState, session.state, session.status),
valuesPrinted: false,
};
}
function compactTargetTaskSummary(targetTask: Record<string, unknown>): Record<string, unknown> {
const workspace = asRecord(targetTask.workspace);
return {
mdtodoId: boundedText(targetTask.mdtodoId, 80),
target: boundedText(targetTask.target, 120),
targetWorkspace: boundedText(targetTask.targetWorkspace, 360),
targetRoute: boundedText(targetTask.targetRoute, 480),
projectId: boundedText(targetTask.projectId, 240),
repository: boundedText(targetTask.repository, 240),
ref: boundedText(targetTask.ref, 240),
verifiedCommit: boundedText(targetTask.verifiedCommit, 160),
refRelation: boundedText(targetTask.refRelation, 120),
workspace: {
branch: boundedText(workspace.branch, 240),
headCommit: boundedText(workspace.headCommit, 160),
clean: booleanOrNull(workspace.clean),
valuesPrinted: false,
},
valuesPrinted: false,
};
}
function compactTargetAipodBinding(binding: Record<string, unknown>): Record<string, unknown> {
const workspaceRef = asRecord(binding.workspaceRef);
const resourceBundleRef = asRecord(binding.resourceBundleRef);
const session = asRecord(binding.session);
const provider = compactCredentialPresence(binding.providerCredentials, "provider", 4);
const tool = compactCredentialPresence(binding.toolCredentials, "tool", 6);
return {
aipod: boundedText(binding.aipod, 160),
node: boundedText(binding.node, 120),
lane: boundedText(binding.lane, 120),
namespace: boundedText(binding.namespace, 160),
projectId: boundedText(binding.projectId, 240),
providerId: boundedText(binding.providerId, 160),
backendProfile: boundedText(binding.backendProfile, 160),
workspaceRef: {
kind: boundedText(workspaceRef.kind, 80),
path: boundedText(workspaceRef.path, 360),
valid: booleanOrNull(workspaceRef.valid),
valuesPrinted: false,
},
resourceBundleRef: {
kind: boundedText(resourceBundleRef.kind, 80),
repoUrl: boundedRepositoryUrl(resourceBundleRef.repoUrl),
ref: boundedText(resourceBundleRef.ref, 240),
inheritedFromAipod: booleanOrNull(resourceBundleRef.inheritedFromAipod),
credentialRefPresent: booleanOrNull(resourceBundleRef.credentialRefPresent),
valuesPrinted: false,
},
session: {
sessionId: boundedText(session.sessionId, 240),
identitySource: boundedText(session.identitySource, 120),
valuesPrinted: false,
},
credentialRefs: {
totalCount: provider.count + tool.count,
allPresent: provider.missingCount + tool.missingCount === 0,
provider,
tool,
valuesPrinted: false,
},
valuesPrinted: false,
};
}
function compactCredentialPresence(value: unknown, kind: "provider" | "tool", limit: number): {
count: number;
presentCount: number;
missingCount: number;
omittedCount: number;
items: Record<string, unknown>[];
valuesPrinted: false;
} {
const source = asRecords(value);
const presentCount = source.filter((item) => item.bindingPresent === true).length;
const items = source.slice(0, limit).map((item) => {
const secretRef = asRecord(item.secretRef);
return {
...(kind === "provider"
? { profile: boundedText(item.profile, 160) }
: {
tool: boundedText(item.tool, 160),
purpose: boundedText(item.purpose, 200),
}),
secretRef: {
namespace: boundedText(secretRef.namespace, 160),
name: boundedText(secretRef.name, 200),
keys: boundedStrings(secretRef.keys, 6, 80),
valuesPrinted: false,
},
bindingPresent: item.bindingPresent === true,
valuesPrinted: false,
};
});
return {
count: source.length,
presentCount,
missingCount: source.length - presentCount,
omittedCount: source.length - items.length,
items,
valuesPrinted: false,
};
}
function targetTaskIdentityNext(
identity: Record<string, unknown>,
targetTask: Record<string, unknown>,
aipodBinding: Record<string, unknown>,
): Record<string, unknown> {
const taskId = asString(identity.taskId);
const runId = asString(identity.runId);
const commandId = asString(identity.commandId);
const runnerJobId = asString(identity.runnerJobId);
const sessionId = asString(identity.sessionId);
const target = asString(targetTask.target) ?? asString(aipodBinding.node);
const lane = asString(aipodBinding.lane);
const suffix = [
target === null ? null : `--target ${target}`,
lane === null ? null : `--lane ${lane}`,
].filter((value): value is string => value !== null).join(" ");
const scoped = (command: string): string => suffix.length === 0 ? command : `${command} ${suffix}`;
return {
...(taskId === null ? {} : {
task: scoped(`bun scripts/cli.ts agentrun describe task/${taskId}`),
attempts: scoped(`bun scripts/cli.ts agentrun get attempts --task ${taskId}`),
}),
...(runId === null ? {} : {
events: scoped(`bun scripts/cli.ts agentrun events run/${runId} --after-seq 0 --limit 20`),
}),
...(runId === null || commandId === null ? {} : {
result: scoped(`bun scripts/cli.ts agentrun result run/${runId} --command ${commandId}`),
}),
...(runnerJobId === null || runId === null ? {} : {
runner: scoped(`bun scripts/cli.ts agentrun describe runnerjob/${runnerJobId} --run ${runId}`),
}),
...(sessionId === null ? {} : {
logs: scoped(`bun scripts/cli.ts agentrun logs session/${sessionId} --tail 120`),
}),
};
}
function innerData(value: Record<string, unknown>): unknown {
return value.data ?? value;
}
function firstRecord(...values: unknown[]): Record<string, unknown> {
for (const value of values) {
const item = asRecord(value);
if (Object.keys(item).length > 0) return item;
}
return {};
}
function asRecord(value: unknown): Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value)
? value as Record<string, unknown>
: {};
}
function asRecords(value: unknown): Record<string, unknown>[] {
return Array.isArray(value) ? value.map(asRecord).filter((item) => Object.keys(item).length > 0) : [];
}
function asString(value: unknown): string | null {
return typeof value === "string" && value.length > 0 ? value : null;
}
function firstText(maxChars: number, ...values: unknown[]): string | null {
for (const value of values) {
const text = boundedText(value, maxChars);
if (text !== null) return text;
}
return null;
}
function boundedText(value: unknown, maxChars: number): string | null {
const text = asString(value);
if (text === null) return null;
const oneLine = text.replace(/\s+/gu, " ").trim();
return oneLine.length <= maxChars ? oneLine : `${oneLine.slice(0, Math.max(0, maxChars - 1))}`;
}
function boundedStrings(value: unknown, limit: number, maxChars: number): string[] {
return Array.isArray(value)
? value.slice(0, limit).map((item) => boundedText(item, maxChars)).filter((item): item is string => item !== null)
: [];
}
function boundedRepositoryUrl(value: unknown): string | null {
const text = asString(value);
if (text === null) return null;
return boundedText(text.replace(/^(https?:\/\/)[^@/]+@/u, "$1[redacted]@"), 320);
}
function booleanOrNull(value: unknown): boolean | null {
return typeof value === "boolean" ? value : null;
}
function isTargetDispatchDecision(value: string | null): value is TargetDispatchDecision {
return value === "dispatched" || value === "idempotent-replay" || value === "concurrent-replay";
}