Skip to content

Latest commit

 

History

History
917 lines (656 loc) · 21.1 KB

File metadata and controls

917 lines (656 loc) · 21.1 KB

Music Agent Dev Harness 开发指导 v1.2

状态: 与 Architecture v1.2 对齐的实现期工程守则
适用范围: Python Core、第一方 Plugin、Provider、TypeScript UI 边界、Process Runtime、Async Runtime、End-to-End 请求闭环
Python 基线: Python >= 3.10;正式验证矩阵至少覆盖 3.10 / 3.11 / 3.12


1. Harness 的定位

Dev Harness 不是“多写一些测试”,而是把已经冻结的工程原则变成机器可执行的门禁。

它有三个目标:

  1. 阻止架构腐化。 已冻结的依赖方向、插件隔离、ExecutionPolicy 无 LLM、Runtime 类型、配置边界等,不允许因为临时方便被绕过。
  2. 证明 Runtime 语义。 异步任务必须可追踪、可取消、可超时、可回收;Process Plugin 必须遵守 stdin/stdout JSON Lines 协议。
  3. 证明 End-to-End 请求真正闭环。 accepted 不是终态;所有请求都必须最终进入明确终态,并且 UI/Transport 断开、Event 丢失、超时、取消、进程崩溃都不能让请求失联。

核心原则:

所有可以可靠机器验证的架构原则,必须进入 Dev Harness;无法可靠机器判断的设计质量问题,进入 Code Review Guideline。


2. Harness 不是单一目录,而是统一门禁

建议把测试分成三层,而不是把所有内容都塞进 backend/tests/dev_harness/

backend/tests/
├─ dev_harness/                # 静态架构与工程规则
│  ├─ policy.py
│  ├─ test_dependencies.py
│  ├─ test_contracts.py
│  ├─ test_plugins.py
│  ├─ test_structure.py
│  ├─ test_config.py
│  ├─ test_cross_language.py
│  └─ test_async_ownership.py
│
├─ runtime/                    # Runtime 行为契约
│  ├─ test_task_supervisor.py
│  ├─ test_task_queue.py
│  ├─ test_deadline.py
│  ├─ test_cancellation.py
│  ├─ test_process_runtime.py
│  ├─ test_shutdown.py
│  └─ test_recovery.py
│
├─ e2e/                        # 请求生命周期闭环
│  ├─ test_sync_request.py
│  ├─ test_async_request.py
│  ├─ test_task_chain.py
│  ├─ test_transport_reconnect.py
│  └─ test_failure_paths.py
│
└─ ...                         # 普通组件/业务测试

统一入口仍然只有:

python scripts/dev_test.py

dev_test 是 Harness 的产品接口;具体测试目录只是内部组织方式。


3. policy.py 是工程规则唯一配置源

所有静态工程规则集中放在:

backend/tests/dev_harness/policy.py

至少维护:

PLUGIN_CATEGORIES = {
    "playback",
    "playlist",
    "library",
    "recommendation",
    "analysis",
    "conversation",
}

RUNTIME_TYPES = {"builtin", "process"}
ROOT_CONFIG_FILES = {"app.yaml", "plugins.yaml", "providers.yaml"}
GENERIC_DUMP_MODULES = {"utils.py", "helpers.py", "common.py"}

PYTHON_MIN = (3, 10)
VERIFIED_PYTHON_MINORS = ((3, 10), (3, 11), (3, 12))

SOFT_REVIEW_LINES = 350
HARD_MAX_LINES = 500
SIZE_EXCEPTIONS = set()

规则修改必须显式 review。新增 Plugin Category、Runtime Type、根配置文件等,必须先修改 Architecture,再修改 Policy,再实现代码。


4. 模板化数据结构必须类化

以下类型属于稳定、重复、跨边界的数据契约,必须建模为明确 Class/Model,不允许以裸 dict[str, Any] 作为公共接口传播:

InputRequest
ValidatedIntent
ResolvedRequest
TaskDescriptor
ResultBinding
ExecutionDecision
CoreResponse

PluginManifest
PluginResult
PluginError
Observation

RequestState
TaskState
TimeBudget / DeadlineContext
CommittedResult
CoreEvent

Track
PlaylistRef
ProviderRef / ProviderStatus

Python 侧优先使用 Pydantic Model;纯内部、无需 schema/序列化的不可变数据可使用 dataclass(frozen=True)

Harness 要检查:

  • 核心 Contract 是否存在且是正式类;
  • 公开方法是否使用已定义 Contract,而不是匿名 dict;
  • TypeScript 对应类型是否由 Python Contract 生成,而非手写第二份真源;
  • 生成文件是否带 DO NOT EDIT 标记并通过 stale-check。

局部纯转换仍应使用函数,例如 normalize_track()match_alias()build_prompt(),不要用无状态包装类增加认知成本。


5. 静态架构门禁

5.1 依赖方向

必须硬失败:

Plugin A           -X-> Plugin B
Provider           -X-> Plugin
Persistence        -X-> RequestCoordinator
Backend Plugin     -X-> UI
ExecutionPolicy    -X-> LLMProvider / SemanticResolver
Core               -X-> 具体第三方 Plugin 实现

建议通过 AST / import graph 检查,不用字符串 grep 作为主要实现。

错误信息必须可行动,例如:

ARCH001 recommendation plugin imports playback plugin.
Plugins must not call each other directly; use Core Task orchestration.

5.2 异步所有权

生产业务代码禁止裸调用:

asyncio.create_task(...)
threading.Thread(...)
ThreadPoolExecutor(...)

例外只能存在于明确登记的基础设施文件,例如:

core/task_supervisor.py
infra/blocking_executor.py
plugins/runtime.py

Harness 应维护 allowlist,而不是广泛豁免目录。

5.3 Event Loop 约束

Core 默认只有一个主 asyncio event loop。

生产模块禁止任意创建:

asyncio.new_event_loop()
loop.run_until_complete()
asyncio.run()

除应用启动边界或显式测试 fixture 外。

5.4 Plugin Contract

所有 Builtin Plugin 必须属于以下六类之一:

PlaybackPlugin
PlaylistPlugin
LibraryPlugin
RecommendationPlugin
AnalysisPlugin
ConversationPlugin

Manifest 最小字段必须满足:

api_version
plugin.id
plugin.version
plugin.category
runtime.type
capabilities[].id

runtime.type 只能是:

builtin
process

process 必须提供非空、完整的 runtime.command: list[str]

5.5 Process stdout/stderr

Process Plugin:

  • stdout 只能输出 JSON Lines 协议消息;
  • 日志必须写 stderr
  • stdout 中出现非 JSON 协议行视为协议错误;
  • Plugin hello 返回的 plugin_id/api_version/plugin_version 必须与 Manifest 一致。

5.6 配置边界

Core 根配置只允许:

config/app.yaml
config/plugins.yaml
config/providers.yaml

Plugin 自身 Manifest 跟随插件;用户安装/启停状态只写 plugins.yaml,不得回写插件 Manifest。

5.7 文件规模

<= 350 lines    正常
351-500 lines   Harness warning / architecture review
> 500 lines     FAIL

合理例外必须登记在 SIZE_EXCEPTIONS,并附注原因;禁止默默放宽全局上限。


6. Async Runtime Harness

6.1 TaskSupervisor 是所有后台 coroutine 的唯一所有者

任何后台任务必须登记:

request_id
task_id
asyncio.Task handle
created_at
deadline
cancel entry

Runtime test 必须证明:

  • supervisor 创建后能查询 task;
  • task 完成后自动移出 active 集合并保留终态;
  • task 异常不会产生 “Task exception was never retrieved”;
  • cancellation 会进入明确 TaskState;
  • shutdown 能等待/取消所有登记任务;
  • 无 supervisor 持有的 fire-and-forget task 会被 Harness 拦截。

6.2 CoreTaskQueue 必须有界

至少测试:

max_queue_size
max_concurrency
per_plugin_concurrency
interactive > background priority
dedup
QUEUE_FULL backpressure

不要只测试“能 enqueue”;必须证明达到容量后会拒绝,并且不会无限增长。

6.3 Process Plugin V1 并发

每个 Process Plugin 实例默认:

active_invocation <= 1

第二个调用必须由 CoreTaskQueue 排队,而不是同时向同一 stdin 写多条并发 invocation。


7. 时间管理 Harness

7.1 时间必须使用 monotonic clock

超时计算不能基于 wall clock。

推荐 Runtime 提供统一 Clock 抽象:

Clock.monotonic()
Clock.now_utc()       # 仅记录展示/持久化时间

测试必须使用 FakeClock/ManualClock,避免真实 sleep(10) 这类慢且不稳定的测试。

7.2 TimeBudget 必须统一建模

至少包含:

request_deadline
queue_timeout
execution_timeout
process_start_timeout
handshake_timeout
cancel_grace_period
process_idle_timeout
shutdown_grace_period

TaskState 至少记录:

created_at
queued_at
started_at
finished_at
deadline_at

7.3 Deadline 只能递减,不能刷新

必须有 Harness 明确验证:

request deadline = D
Task A consumes 3s
Task B receives remaining(D - elapsed)

Provider/LLM retry 同样必须消费剩余时间,禁止:

retry #1 timeout=30s
retry #2 又 timeout=30s
retry #3 又 timeout=30s

如果剩余预算不足以开始下一次 retry,立即结束为 timeout/failure。

7.4 Queue Timeout 与 Execution Timeout 分离

必须分别测试:

  • 任务排队过久 -> TIMED_OUT,插件从未启动;
  • 已开始执行但超时 -> 执行取消路径;
  • queue timeout 不能偷用 execution timeout;
  • execution timeout 在 started_at 后才开始计算。

8. 线程管理 Harness

线程只允许解决短时、阻塞、无法原生 async 的调用

统一由 Core BoundedBlockingExecutor 管理;插件禁止自行创建线程池。

必须测试:

max_workers 固定且来自配置
超过 worker 数的调用排队而不是创建新线程
取消请求不会伪装成“线程已被强杀”
晚到线程结果在 task 已 CANCELLED/TIMED_OUT 时被丢弃

原则:

Python 线程不能可靠强杀,所以任何需要强制 timeout/cancel 的长任务都必须迁移到 process runtime。


9. Process Runtime Harness

Process Runtime 必须用真实测试子进程验证,不用纯 mock 替代。

测试 fixture 建议:

backend/tests/fixtures/process_echo_plugin.py
backend/tests/fixtures/process_slow_plugin.py
backend/tests/fixtures/process_crash_plugin.py
backend/tests/fixtures/process_bad_stdout_plugin.py

覆盖:

spawn
hello handshake
invoke/result
progress(optional)
cancel
shutdown
process_start_timeout
handshake_timeout
execution_timeout
crash detection
bad stdout protocol
stderr logging
idle shutdown
restart on next invocation

强制取消顺序:

cancel message
 -> wait cancel_grace_period
 -> terminate
 -> wait short grace
 -> kill if still alive

Process 超时或取消后晚到结果必须被丢弃,禁止将终态从 TIMED_OUT/CANCELLED 改回 COMPLETED


10. 请求状态机 Harness

CoreResponse(status=accepted, terminal=false) 只是 ACK。

Request 必须最终进入以下终态之一:

COMPLETED
FAILED
CANCELLED
TIMED_OUT
REJECTED
INTERRUPTED

Task 也必须有自己的终态,不得只靠 Event 推断。

Harness 要验证非法状态跳转,例如:

COMPLETED -> RUNNING      禁止
CANCELLED -> COMPLETED    禁止
TIMED_OUT -> COMPLETED    禁止

11. State Closure Harness

采用 Architecture v1.2 的方案 C:

PluginResult
 -> ResultValidator
 -> StateCoordinator
 -> RuntimeStateStore / SQLite
 -> CommittedResult
 -> CoreEvent
 -> CoreResponse / UI notification

必须证明:

  1. 状态提交早于 completed 响应;
  2. Event 只通知已经提交的事实;
  3. Event 发送失败不能回滚已经确认的业务终态;
  4. UI 收到完成事件时,查询 RuntimeStateStore/SQLite 必须已经得到新状态;
  5. Plugin 不能直接写全局 RuntimeStateStore。

12. E2E 生命周期门禁

12.1 同步快路径

固定验收链:

“暂停”
 -> IntentRouter
 -> ContextResolver
 -> TaskDescriptor
 -> ExecutionPolicy
 -> PlaybackPlugin(fake/builtin)
 -> PluginResult
 -> StateCoordinator
 -> CoreResponse(completed, terminal=true)

必须断言 request_id/task_id 全程一致,且状态提交早于最终响应。

12.2 异步 Process 正常路径

request
 -> RequestState.CREATED
 -> queue
 -> CoreResponse(accepted, terminal=false)
 -> TaskSupervisor owns task
 -> process spawn/handshake
 -> invoke/result
 -> StateCoordinator commit
 -> TaskState.COMPLETED
 -> RequestState.COMPLETED
 -> completion Event

12.3 异步失败矩阵

至少覆盖:

queue timeout
execution timeout
user cancellation
process crash
queue full/backpressure
application shutdown
provider retry exhausted
ResultBinding failure
plugin invalid result

每一种都必须产生可查询的最终 RequestState/CoreResponse 语义,禁止 silent drop。


13. Transport 断线与重连

异步任务 ownership 属于 Core,不属于某个 WebSocket/HTTP connection。

Harness 必须模拟:

request -> accepted
transport disconnect
background task completes
transport reconnect
query request_id

重连后必须能够取得最终状态/结果。

断线默认不取消 accepted task;只有显式用户 cancellation 才取消。


14. Event 丢失不能导致结果丢失

事件不是事实源。

异步任务完成顺序必须是:

persist terminal RequestState/TaskState + result
 -> commit
 -> emit completion event

Harness 应故意让 EventBus 发布失败,然后验证:

get_request(request_id)

仍能返回完整终态。


15. 应用 Shutdown Harness

关闭必须是受控流程:

stop accepting new external requests
 -> reject/cancel queued work as policy defines
 -> request cancellation of running tasks
 -> wait shutdown_grace_period
 -> terminate/kill remaining process workers
 -> persist terminal/interrupted state
 -> flush storage
 -> close event loop/resources

必须测试:

  • shutdown 后没有未回收 TaskSupervisor task;
  • 没有遗留 Process child;
  • queued task 不会在 shutdown 后突然启动;
  • 非终态记录得到明确 CANCELLEDINTERRUPTED
  • shutdown 超时不会永久阻塞应用退出。

16. 异常退出与启动恢复

V1 不自动恢复未完成任务。

应用启动时:

SQLite 中仍为 QUEUED/RUNNING/CANCELLING 的旧 Request/Task
 -> 标记 INTERRUPTED

Harness 必须用预置数据库记录验证该行为。

禁止在没有完整可恢复执行上下文的情况下“猜测继续执行”。


17. TaskChain Harness

跨插件合作只允许 Core TaskChain。

上一步数据进入下一步必须由 ResultBinding 显式描述,例如:

task_1.data.tracks[0]
 -> task_2.input.track

必须测试:

  • binding 成功;
  • source path 不存在 -> chain FAILED;
  • schema 不兼容 -> chain FAILED;
  • Task A timeout/cancel/fail -> Task B 不启动;
  • 整个 chain 共享剩余 request deadline;
  • Recommendation 完成不会因为存在 PlaybackPlugin 而隐式播放。

18. Provider / LLM Harness

公共 LLM Provider 支持:

商业 API Key 模式
endpoint + model 模式

Harness 重点不测试真实商业 API,而测试 Core 约束:

  • Plugin 不读取 API Key 配置;
  • Plugin 只通过公共 Provider 接口调用模型;
  • Provider 接受剩余 deadline;
  • retry 不刷新 deadline;
  • provider timeout 映射到明确 Task failure/timeout;
  • endpoint/model 配置模型可在 Python 3.10/3.11/3.12 正确解析。

真实 Provider API 使用契约测试/可选 integration profile,不进入默认快速 dev-test。


19. Python 兼容矩阵

pyproject.toml 必须声明:

requires-python = ">=3.10"

正式 Harness/CI 至少执行:

Python 3.10
Python 3.11
Python 3.12

必须在每个版本运行的核心集合:

contracts
plugin manifest/runtime
TaskSupervisor/CoreTaskQueue
Process Runtime
StateCoordinator
sync E2E
async E2E
TaskChain/deadline tests

本地如果只安装一个 Python 版本,scripts/dev_test.py 运行当前解释器完整门禁;CI 再负责三版本矩阵。

更新 Python 版本不是禁止的,但未经矩阵验证的版本不称为“正式验证版本”。


20. TypeScript 边界 Harness

TypeScript UI 只通过一个 Core transport facade 访问后端:

frontend/src/transport/core-client.ts

禁止 feature/component 直接拼 Python HTTP/WebSocket/IPC 调用。

Python Contract 为唯一 Source of Truth:

Python Pydantic Model
 -> JSON Schema / generator
 -> frontend/src/types/generated/contracts.ts

Harness 必须检查生成文件是否 stale。

TypeScript 自身继续使用:

tsc
ESLint
frontend unit tests

不要再手写一套重复架构规则。


21. 统一 dev_test 执行顺序

推荐 scripts/dev_test.py 依次执行:

1. architecture static harness
2. Python contract/component tests
3. async/runtime tests
4. E2E lifecycle tests
5. Python compile/static checks
6. generated TypeScript contract stale-check
7. TypeScript typecheck
8. ESLint / frontend unit tests(前端开始实现后启用)

任何一步失败立即停止,并显示具体 Harness rule ID。

本地快速模式可支持:

python scripts/dev_test.py --fast

--fast 只能跳过明确的慢 integration profile,不能跳过架构 Harness、核心 Runtime、E2E 生命周期测试。


22. 测试实现原则

22.1 时间测试不用真实长 sleep

统一使用 FakeClock/ManualClock 或可注入 monotonic clock。

22.2 Process 行为使用真实轻量子进程

IPC、cancel、stdout protocol、crash 等必须使用 fixture process;mock 无法证明真正的进程边界。

22.3 E2E 使用 Fake Plugin / Fake Provider,但走真实 Core Pipeline

Fake 的只是外部副作用,不是假掉:

Intent -> Context -> Task -> Execution -> PluginManager -> StateCoordinator -> CoreResponse

22.4 每个测试只证明一个行为

测试名应该能直接说明架构要求,例如:

test_accepted_async_request_eventually_reaches_terminal_state
test_cancelled_task_rejects_late_plugin_result
test_provider_retry_does_not_reset_deadline
test_state_is_committed_before_completion_event
test_transport_disconnect_does_not_cancel_accepted_task

23. Architecture TDD 工作流

任何会改变架构边界的开发遵循:

Architecture decision
 -> update Harness rule/test first
 -> verify RED
 -> implement minimum change
 -> verify GREEN
 -> refactor
 -> run full dev_test

例如未来新增一个 Plugin Category:

  1. 先更新 Architecture;
  2. 修改 PLUGIN_CATEGORIES
  3. 写该类别合法/非法行为测试;
  4. 运行红测;
  5. 实现 Base Category Class/Manifest;
  6. 全量 dev-test。

禁止先改业务代码,再为了让 CI 通过而事后放宽 Harness。


24. Code Review Guideline:不适合机器硬判的规则

以下内容不建议写成脆弱测试,而应在 review 中检查:

  • 类是否真的表达稳定语义,而不是无意义包装;
  • 模块命名是否准确;
  • 抽象是否过早;
  • Plugin 是否承担了不属于自己的业务责任;
  • Prompt 是否应该拆分;
  • 某文件 300 行是否已经难读,即使没有触发 500 行硬限制;
  • 是否出现不必要的新依赖;
  • 是否有更简单的实现能保持同样契约。

Harness 用于保护确定边界,Code Review 用于保护设计质量。


25. v1.2 必须存在的 Harness 验收清单

在进入真实业务插件开发前,至少保证以下门禁全部存在并转绿:

ARCH-01  Plugin dependency isolation
ARCH-02  ExecutionPolicy has no LLM dependency
ARCH-03  six frozen plugin categories only
ARCH-04  runtime only builtin/process
ARCH-05  process command is complete list[str]
ARCH-06  contract models are classes/models
ARCH-07  no generic dump modules
ARCH-08  file-size budget
ARCH-09  Python -> TypeScript contract single source
ARCH-10  no bare fire-and-forget task/thread

ASYNC-01 TaskSupervisor owns every background coroutine
ASYNC-02 bounded queue and backpressure
ASYNC-03 single active invocation per process plugin instance
ASYNC-04 queue timeout
ASYNC-05 execution timeout
ASYNC-06 remaining-deadline propagation
ASYNC-07 provider retry does not reset deadline
ASYNC-08 cancellation and late-result rejection
ASYNC-09 controlled shutdown
ASYNC-10 interrupted-state recovery

E2E-01 sync playback fast path closes with CoreResponse
E2E-02 async process request accepted -> terminal
E2E-03 transport reconnect can query terminal result
E2E-04 event loss does not lose terminal state
E2E-05 TaskChain uses explicit ResultBinding
E2E-06 chain failure prevents downstream execution

COMPAT-01 Python >=3.10 metadata
COMPAT-02 verified matrix 3.10/3.11/3.12

这些测试不是为了追求覆盖率数字,而是为了保证已经冻结的 Architecture v1.2 在后续开发中不会逐渐失真。


26. 最终开发原则

Harness 需要长期坚持四条纪律:

  1. Contract first。 稳定模板数据先建模,再传递。
  2. Ownership explicit。 task、thread、process、state 都必须有明确 Core owner。
  3. Deadline monotonic。 时间预算只能消耗,不能在内部重试或 TaskChain 中偷偷刷新。
  4. Terminal state authoritative。 Event/UI 只是通知面;RequestState/TaskState + committed result 才是异步请求的权威事实。

只要这四条和统一 dev_test 一直成立,后续 Plugin 数量增长、LLM Provider 增加、Analysis/Recommendation 复杂化,都不会轻易破坏 Core 的可维护性。