feat(contract): minimal public execution contract + conformance suite (U7a)
ci / vet-and-test (push) Failing after 0s
ci / vet-and-test (push) Failing after 0s
- contract: Request/Result/Outcome(4-state)/Error(taxonomy+side-effects)/ Capability/BackendDescriptor/Backend/Resolver per plan KTD9/KTD10 - conformance: reusable behavior suite + deterministic fake - semantics: single ExecutionID across retry/fallback, no silent fallback, explicit side-effect classification, single-owner retry layering, workspace path authorization is not an OS sandbox - CI: go vet+test on host runner (.gitea/workflows)
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
name: ci
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
jobs:
|
||||
vet-and-test:
|
||||
runs-on: nightly
|
||||
steps:
|
||||
- run: go vet ./... && go test -count=1 ./...
|
||||
@@ -0,0 +1,37 @@
|
||||
# Agent-Native
|
||||
|
||||
可复用的 Agent 执行基础:自研执行内核(runtime)、模型/工具能力接入、外部 Agent
|
||||
适配器(现有 PI Runner 等)与共用契约验收(conformance)。
|
||||
|
||||
## 仓库分层(U7a 首切片:仅 contract + conformance)
|
||||
|
||||
| 层 | 职责 | 依赖边界 |
|
||||
|---|---|---|
|
||||
| contract | 执行契约:Request/Result/Outcome/Error/Capability/Backend/Resolver | 不依赖内核、产品类型、数据库、配置 |
|
||||
| runtime | agentx 演进的多轮循环、工具调度、模型调用、执行会话(U7b 迁入) | 实现 contract;不导入 Core/Squad |
|
||||
| adapters | PI Runner transport / typed-result-v1(U7b 迁入) | 显式映射外部差异,不伪造缺失能力 |
|
||||
| conformance | 所有 Backend 共用的行为验收 | fake 验证契约,真实 adapter 验证能力 |
|
||||
|
||||
规范后端名:`agent-native`(`pi-native` 为兼容别名,由 Core 侧注册表解析)。
|
||||
|
||||
## 关键语义(详见 contract 包 doc.go)
|
||||
|
||||
- ExecutionID 一次逻辑执行唯一;重试与受控 fallback 复用。
|
||||
- Outcome 四态:Succeeded/Failed/Canceled/Unknown;Unknown 不静默降级。
|
||||
- 失败副作用显式分类(none/possible);possible 禁止无条件重放。
|
||||
- 重试单一责任层:步骤级归调用方,传输级有界归 adapter,turn 上限归 runtime。
|
||||
- Workspace 路径授权 ≠ OS 沙箱。
|
||||
|
||||
## 消费方式(首期)
|
||||
|
||||
外层组合(Agent-System 主仓 gitlink)固定精确 SHA,Go 相邻 replace 引用:
|
||||
|
||||
```
|
||||
replace agent-native => ../agent-native
|
||||
```
|
||||
|
||||
构建须 `GOWORK=off`;不通过 go get 路由依赖(实例 URL 带端口,见主仓方案 KTD3)。
|
||||
|
||||
## CI
|
||||
|
||||
`.gitea/workflows/ci.yml`:go vet + go test(host runner)。
|
||||
@@ -0,0 +1,147 @@
|
||||
// Package conformance 提供所有 Backend 实现共用的行为验收套件。
|
||||
//
|
||||
// 用法:实现方在自己的测试中调用 conformance.Run(t, backend, conformance.Case{...})。
|
||||
// fake 后端用于契约自测;真实实现(runtime、PI adapter)必须通过同一套件,
|
||||
// fake 通过不等于真实适配完成(分别记证据)。
|
||||
package conformance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"agent-native/contract"
|
||||
)
|
||||
|
||||
// FakeBackend 是确定性假实现,仅用于契约套件自测与消费方单测。
|
||||
// 不模拟任何模型行为;按 Script 逐项返回。
|
||||
type FakeBackend struct {
|
||||
Desc contract.BackendDescriptor
|
||||
// Result 为每次 Execute 返回的终态(浅拷贝后返回)。
|
||||
Result contract.Result
|
||||
// BlockUntil 若非 nil,Execute 阻塞直到该 channel 关闭或 ctx 取消,
|
||||
// 用于验证取消语义。
|
||||
BlockUntil <-chan struct{}
|
||||
// CanceledWithCtx 记录 Execute 返回时 ctx 是否已取消。
|
||||
CanceledWithCtx bool
|
||||
// LastRequest 记录最近一次请求。
|
||||
LastRequest contract.Request
|
||||
}
|
||||
|
||||
func (f *FakeBackend) Descriptor() contract.BackendDescriptor { return f.Desc }
|
||||
|
||||
func (f *FakeBackend) Execute(ctx context.Context, req contract.Request) (*contract.Result, error) {
|
||||
f.LastRequest = req
|
||||
if f.BlockUntil != nil {
|
||||
select {
|
||||
case <-f.BlockUntil:
|
||||
case <-ctx.Done():
|
||||
f.CanceledWithCtx = true
|
||||
res := *(&f.Result)
|
||||
res.Outcome = contract.OutcomeCanceled
|
||||
res.Err = &contract.Error{
|
||||
Code: contract.ErrCanceled,
|
||||
SideEffects: contract.SideEffectNone,
|
||||
}
|
||||
return &res, nil
|
||||
}
|
||||
}
|
||||
res := f.Result
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
// Case 是一条契约验收用例。
|
||||
type Case struct {
|
||||
Name string
|
||||
// Backend 为被测实现。
|
||||
Backend contract.Backend
|
||||
// Request 为请求模板。
|
||||
Request contract.Request
|
||||
// CancelAfter 若非零,在等待该时长后取消 ctx(验证取消路径)。
|
||||
CancelAfter time.Duration
|
||||
// WantOutcome 为期望终态。
|
||||
WantOutcome contract.Outcome
|
||||
// WantErrCode 为期望错误码(Failed/Canceled/Unknown 时)。
|
||||
WantErrCode contract.ErrorCode
|
||||
}
|
||||
|
||||
// Run 执行一条契约用例。
|
||||
func Run(t *testing.T, c Case) {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
var cancel context.CancelFunc
|
||||
if c.CancelAfter > 0 {
|
||||
ctx, cancel = context.WithTimeout(ctx, c.CancelAfter)
|
||||
defer cancel()
|
||||
}
|
||||
res, err := c.Backend.Execute(ctx, c.Request)
|
||||
if err != nil {
|
||||
t.Fatalf("[%s] Execute returned transport error %v; business outcomes must be in Result", c.Name, err)
|
||||
}
|
||||
if res == nil {
|
||||
t.Fatalf("[%s] Execute returned nil Result", c.Name)
|
||||
}
|
||||
if res.Outcome != c.WantOutcome {
|
||||
t.Fatalf("[%s] Outcome = %q, want %q", c.Name, res.Outcome, c.WantOutcome)
|
||||
}
|
||||
if c.WantErrCode != "" {
|
||||
if res.Err == nil {
|
||||
t.Fatalf("[%s] expected error code %q, got nil Err", c.Name, c.WantErrCode)
|
||||
}
|
||||
if res.Err.Code != c.WantErrCode {
|
||||
t.Fatalf("[%s] Err.Code = %q, want %q", c.Name, res.Err.Code, c.WantErrCode)
|
||||
}
|
||||
if res.Err.SideEffects == "" {
|
||||
t.Fatalf("[%s] Err.SideEffects must be explicit (none|possible), got empty", c.Name)
|
||||
}
|
||||
}
|
||||
if res.Outcome == contract.OutcomeSucceeded && res.Err != nil {
|
||||
t.Fatalf("[%s] Succeeded result must not carry Err", c.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Suite 是最小契约套件:成功、失败带副作用标记、取消三态。
|
||||
// 每个真实 Backend 实现都必须通过;套件外按实现能力补充专项用例。
|
||||
func Suite(t *testing.T, mk func() contract.Backend) {
|
||||
t.Helper()
|
||||
|
||||
t.Run("succeeded_minimal", func(t *testing.T) {
|
||||
b := mk()
|
||||
Run(t, Case{
|
||||
Name: "succeeded",
|
||||
Backend: b,
|
||||
Request: contract.Request{ExecutionID: "e1", Prompt: "hi"},
|
||||
WantOutcome: contract.OutcomeSucceeded,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("descriptor_has_name", func(t *testing.T) {
|
||||
b := mk()
|
||||
d := b.Descriptor()
|
||||
if d.Name == "" {
|
||||
t.Fatal("Descriptor().Name must not be empty")
|
||||
}
|
||||
if len(d.Capabilities) == 0 {
|
||||
t.Fatal("Descriptor().Capabilities must declare at least one capability")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("error_sideeffects_explicit", func(t *testing.T) {
|
||||
b := mk()
|
||||
// 通过描述能力判定是否有失败路径可测;无失败路径的实现跳过。
|
||||
if !b.Descriptor().Has(contract.CapText) && !b.Descriptor().Has(contract.CapFiles) {
|
||||
t.Skip("no text/files capability to force failure path")
|
||||
}
|
||||
// 空提示在多数实现下是确定性失败或成功空输出;仅验证当 Outcome=Failed
|
||||
// 时错误字段完整。具体实现应补充自身失败注入用例。
|
||||
res, _ := b.Execute(context.Background(), contract.Request{ExecutionID: "e2"})
|
||||
if res != nil && res.Outcome == contract.OutcomeFailed {
|
||||
if res.Err == nil || res.Err.Code == "" || res.Err.SideEffects == "" {
|
||||
t.Fatalf("failed result must carry Code and SideEffects, got %+v", res.Err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
_ = fmt.Sprint() // keep fmt import stable for future diagnostics
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
// Package contract 定义 Agent-Native 的公共执行契约。
|
||||
//
|
||||
// 本包是助手(Core)与小队(Squad)共用的唯一执行接口层:Core 注入具体
|
||||
// Backend 实现,Squad 只表达需求并使用已注入的后端。契约不依赖任何具体
|
||||
// 内核、产品类型、数据库或配置包。
|
||||
//
|
||||
// 语义不变量(实现方与消费方都必须遵守):
|
||||
//
|
||||
// 1. ExecutionID 在一次逻辑执行内唯一;步骤重试与受控 fallback 必须复用
|
||||
// 同一个 ExecutionID,并共享预算与截止时间。
|
||||
// 2. Outcome 取值 Succeeded/Failed/Canceled/Unknown 四态之一;Unknown 表示
|
||||
// 结果不确定(可能已有副作用),绝不静默降级为 Succeeded 或 Failed。
|
||||
// 3. 重试只有一个责任层:步骤级重试归调用方(Squad),传输级有界重试归
|
||||
// adapter,循环 turn 上限归 runtime;不得多层重试产生重复写入。
|
||||
// 4. 取消必须传播到模型请求、工具调用与子进程;不支持可靠取消的实现必须
|
||||
// 报告 cancel_requested 并保持 Unknown,不得伪报 Canceled。
|
||||
// 5. 原生执行失败且已有文件或外部动作、或结果 Unknown 时,禁止无条件换
|
||||
// 后端重放;是否可重放由调用方依据 Error.SideEffects 判定。
|
||||
// 6. 执行 transcript 与产品聊天历史不是同一账本;SessionScope 仅用于隔离
|
||||
// 上下文,不授予跨会话可见性。
|
||||
// 7. Workspace 的路径约束不是 OS 沙箱;执行不可信第三方工具需要调用方
|
||||
// 提供的进程/容器隔离,不能由本契约推导。
|
||||
package contract
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Capability 声明后端可验证的能力。解析器按任务需求选择实现;必须能力
|
||||
// 缺失时拒绝执行,而不是静默降级到 Stub。
|
||||
type Capability string
|
||||
|
||||
const (
|
||||
// CapText 表示能返回非空文本结果(对话、摘要等纯文本任务)。
|
||||
CapText Capability = "text"
|
||||
// CapFiles 表示能按 Targets 交付文件候选产物(reply.md/output 流程)。
|
||||
CapFiles Capability = "files"
|
||||
// CapStream 表示支持过程事件流(OnEvent 回调有实际内容)。
|
||||
CapStream Capability = "stream"
|
||||
// CapTools 表示支持受控工具调用(受 ToolPolicy 约束)。
|
||||
CapTools Capability = "tools"
|
||||
// CapCancel 表示支持可靠取消(取消传播到模型/工具/子进程)。
|
||||
CapCancel Capability = "cancel"
|
||||
// CapResume 表示支持跨进程恢复执行(配合 ExecutionJournal 端口)。
|
||||
CapResume Capability = "resume"
|
||||
// CapUsage 表示能报告 token 用量。
|
||||
CapUsage Capability = "usage"
|
||||
// CapStructuredOutput 表示支持结构化(非纯文本)输出。
|
||||
CapStructuredOutput Capability = "structured-output"
|
||||
)
|
||||
|
||||
// ToolProfile 是预定义工具策略名;与现有产品配置兼容。
|
||||
const (
|
||||
ToolProfileWorkspace = "workspace" // 默认:文档类工具受限集合
|
||||
ToolProfileReasoning = "reasoning" // 无工具,仅推理
|
||||
)
|
||||
|
||||
// ToolPolicy 描述执行允许的工具范围。实现不得自行扩展;未知工具一律拒绝。
|
||||
type ToolPolicy struct {
|
||||
// Profile 为策略名(workspace/reasoning/或产品注册的自定义名)。
|
||||
Profile string `json:"profile"`
|
||||
// Allowlist 显式列出额外允许的工具名;与 Profile 取交集语义由实现声明,
|
||||
// 未声明时视为仅 Allowlist。
|
||||
Allowlist []string `json:"allowlist,omitempty"`
|
||||
}
|
||||
|
||||
// WorkspaceHandle 是调用方已授权的工作区句柄。Root 之外的路径访问必须失败。
|
||||
// 它表达路径授权,不构成操作系统级隔离。
|
||||
type WorkspaceHandle struct {
|
||||
// Root 为授权根目录的绝对路径(已归一化)。
|
||||
Root string `json:"root"`
|
||||
// Writable 声明允许写入的相对路径集合;空表示由产品规则决定(兼容现状)。
|
||||
Writable []string `json:"writable,omitempty"`
|
||||
}
|
||||
|
||||
// SessionScope 隔离执行上下文:不同 tenant/project/session 的会话、transcript、
|
||||
// 授权与预算互不可见。字段为产品侧标识,契约层只要求非空时保持隔离。
|
||||
type SessionScope struct {
|
||||
Tenant string `json:"tenant,omitempty"`
|
||||
Project string `json:"project,omitempty"`
|
||||
Session string `json:"session,omitempty"`
|
||||
}
|
||||
|
||||
// Budget 约束一次执行(含共享 ExecutionID 的重试)。
|
||||
type Budget struct {
|
||||
// MaxPromptTokens/MaxCompletionTokens 为 0 时不限制。
|
||||
MaxPromptTokens int `json:"max_prompt_tokens,omitempty"`
|
||||
MaxCompletionTokens int `json:"max_completion_tokens,omitempty"`
|
||||
// Deadline 为截止时间;零值表示无外部截止(仍受 ctx 控制)。
|
||||
Deadline time.Time `json:"deadline,omitempty"`
|
||||
}
|
||||
|
||||
// Message 是可选的历史上下文(多轮对话/延续任务)。实现可以忽略,但不得
|
||||
// 跨 SessionScope 混入。
|
||||
type Message struct {
|
||||
Role string `json:"role"` // user | assistant | tool | system
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
// Request 是一次授权执行的统一输入。字段为设计契约;现有 file-only 流程
|
||||
// (Prompt+Instructions+Targets→reply.md)是其子集 facade。
|
||||
type Request struct {
|
||||
// ExecutionID 由调用方生成,重试与受控 fallback 复用同值。
|
||||
ExecutionID string `json:"execution_id"`
|
||||
// SessionScope 为隔离身份;可为零值(一次性任务)。
|
||||
SessionScope SessionScope `json:"session_scope"`
|
||||
|
||||
Prompt string `json:"prompt"`
|
||||
Instructions string `json:"instructions,omitempty"`
|
||||
// Context 为可选历史消息;实现不得将其与 Prompt 之外的数据混合。
|
||||
Context []Message `json:"context,omitempty"`
|
||||
|
||||
// Targets 为期望交付的文件相对路径(文件任务);纯文本任务可空。
|
||||
Targets []string `json:"targets,omitempty"`
|
||||
|
||||
// Workspace 为授权工作区;文件任务的读写都必须限制在 Root 内。
|
||||
Workspace WorkspaceHandle `json:"workspace"`
|
||||
// ToolPolicy 为工具范围;空 Profile 等同 reasoning。
|
||||
ToolPolicy ToolPolicy `json:"tool_policy"`
|
||||
// Budget 为本次执行预算。
|
||||
Budget Budget `json:"budget"`
|
||||
// Required 为必须满足的能力;解析器据此选择实现,缺失即拒绝。
|
||||
Required []Capability `json:"required,omitempty"`
|
||||
|
||||
// OnEvent 为可选过程事件回调(nil = 调用方不需要过程流)。
|
||||
// 回调必须在 Execute 返回前完成;回调内不得阻塞。
|
||||
OnEvent func(Event) `json:"-"`
|
||||
}
|
||||
|
||||
// Outcome 是执行终态。Unknown 优先于乐观判定:凡无法确认结果(含进程崩溃、
|
||||
// 传输中断后未收到终态)一律 Unknown,由调用方恢复。
|
||||
type Outcome string
|
||||
|
||||
const (
|
||||
OutcomeSucceeded Outcome = "succeeded"
|
||||
OutcomeFailed Outcome = "failed"
|
||||
OutcomeCanceled Outcome = "canceled"
|
||||
OutcomeUnknown Outcome = "unknown"
|
||||
)
|
||||
|
||||
// EventKind 过程事件类型(与现有 ProcessEvent 投影兼容)。
|
||||
type EventKind string
|
||||
|
||||
const (
|
||||
EventTextDelta EventKind = "text_delta"
|
||||
EventReasoningDelta EventKind = "reasoning_delta"
|
||||
EventToolCallDelta EventKind = "tool_call_delta"
|
||||
EventToolExecStart EventKind = "tool_execution_start"
|
||||
EventToolExecEnd EventKind = "tool_execution_end"
|
||||
EventMessageEnd EventKind = "message_end"
|
||||
)
|
||||
|
||||
// Event 是过程事件帧;消费方(Core)负责映射为 SSE/持久化事件。
|
||||
type Event struct {
|
||||
Kind EventKind `json:"kind"`
|
||||
Text string `json:"text,omitempty"` // *_delta 文本
|
||||
Tool string `json:"tool,omitempty"` // tool_* 工具名
|
||||
Detail string `json:"detail,omitempty"` // 工具参数/结果摘要
|
||||
}
|
||||
|
||||
// SideEffects 描述失败时的副作用状态,决定是否允许重试/fallback 重放。
|
||||
type SideEffects string
|
||||
|
||||
const (
|
||||
// SideEffectNone:确认未产生任何外部副作用(未派发或纯读取后失败)。
|
||||
SideEffectNone SideEffects = "none"
|
||||
// SideEffectPossible:可能已有副作用(文件已写、外部工具已调用、
|
||||
// 终态未知)。禁止无条件重放;恢复判断归调用方。
|
||||
SideEffectPossible SideEffects = "possible"
|
||||
)
|
||||
|
||||
// ErrorCode 稳定错误分类。未知代码按 Unknown 处理。
|
||||
type ErrorCode string
|
||||
|
||||
const (
|
||||
ErrCanceled ErrorCode = "canceled"
|
||||
ErrDeadlineExceeded ErrorCode = "deadline_exceeded"
|
||||
ErrBudgetExceeded ErrorCode = "budget_exceeded"
|
||||
ErrProviderUnavailable ErrorCode = "provider_unavailable"
|
||||
ErrProviderRejected ErrorCode = "provider_rejected"
|
||||
ErrToolRejected ErrorCode = "tool_rejected"
|
||||
ErrPathOutsideWorkspace ErrorCode = "path_outside_workspace"
|
||||
ErrMissingCapability ErrorCode = "missing_capability"
|
||||
ErrMissingDeliverable ErrorCode = "missing_deliverable"
|
||||
ErrEmptyOutput ErrorCode = "empty_output"
|
||||
ErrUnknownOutcome ErrorCode = "unknown_outcome"
|
||||
ErrInternal ErrorCode = "internal"
|
||||
)
|
||||
|
||||
// Error 是标准化执行错误。Retryable 仅表示传输级瞬时故障可由 adapter 有界
|
||||
// 重试或调用方作为新步骤重试;与 SideEffects 独立判定。
|
||||
type Error struct {
|
||||
Code ErrorCode `json:"code"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Retryable bool `json:"retryable,omitempty"`
|
||||
SideEffects SideEffects `json:"side_effects"`
|
||||
}
|
||||
|
||||
func (e *Error) Error() string { return string(e.Code) + ": " + e.Reason }
|
||||
|
||||
// Usage 报告 token 用量;实现报告不了时保持零值,不猜测。
|
||||
type Usage struct {
|
||||
PromptTokens int `json:"prompt_tokens,omitempty"`
|
||||
CompletionTokens int `json:"completion_tokens,omitempty"`
|
||||
}
|
||||
|
||||
// Result 是执行终态。Text 与 Files 依据任务可为空;Outcome 决定解释。
|
||||
type Result struct {
|
||||
Outcome Outcome `json:"outcome"`
|
||||
// Text 为文本结果(CapText 任务必填)。
|
||||
Text string `json:"text,omitempty"`
|
||||
// Files 为文件候选产物(内容字节),由调用方决定正式落盘与提交。
|
||||
Files map[string][]byte `json:"files,omitempty"`
|
||||
// Logs 为执行日志/过程摘要(兼容现有 Result.Logs)。
|
||||
Logs string `json:"logs,omitempty"`
|
||||
// Err 为 Failed/Canceled/Unknown 时的标准化错误;Succeeded 时为 nil。
|
||||
Err *Error `json:"err,omitempty"`
|
||||
// Usage 为 token 用量。
|
||||
Usage Usage `json:"usage"`
|
||||
}
|
||||
|
||||
// BackendDescriptor 声明实现身份与可验证能力;前端只展示经授权的描述。
|
||||
type BackendDescriptor struct {
|
||||
// Name 为规范后端名(如 agent-native、pi-agent)。规范名 agent-native;
|
||||
// pi-native 为兼容别名,注册表负责别名解析与弃用周期。
|
||||
Name string `json:"name"`
|
||||
// Capabilities 为该实现声明且经验证的能力集合。
|
||||
Capabilities []Capability `json:"capabilities"`
|
||||
// AdapterVersion 标识实现版本(内核或适配器),用于收据与回退审计。
|
||||
AdapterVersion string `json:"adapter_version,omitempty"`
|
||||
}
|
||||
|
||||
// Has 报告描述是否声明指定能力。
|
||||
func (d BackendDescriptor) Has(c Capability) bool {
|
||||
for _, x := range d.Capabilities {
|
||||
if x == c {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Backend 是统一执行接口。实现必须:
|
||||
// - 在 ctx 取消时尽快返回(Canceled 或 Unknown);
|
||||
// - 不持有跨执行的可变会话状态(连接池等无状态资源可复用);
|
||||
// - 不自行扩大工具范围或读取 Workspace 之外的路径。
|
||||
type Backend interface {
|
||||
Descriptor() BackendDescriptor
|
||||
// Execute 执行一次授权调用。返回的 Result 永不为 nil;ctx 错误映射为
|
||||
// Outcome=Canceled/Unknown,不返回 Go error 表达业务终态。
|
||||
Execute(ctx context.Context, req Request) (*Result, error)
|
||||
}
|
||||
|
||||
// Resolver 按名称与必需能力解析后端。未知名必须返回错误,生产路径不得
|
||||
// 回落到 Stub。
|
||||
type Resolver interface {
|
||||
Resolve(name string, required []Capability) (Backend, error)
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package contract_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"agent-native/conformance"
|
||||
"agent-native/contract"
|
||||
)
|
||||
|
||||
// 契约自测:fake 后端通过最小套件,验证套件本身与类型语义一致。
|
||||
func TestFakeBackendSuite(t *testing.T) {
|
||||
conformance.Suite(t, func() contract.Backend {
|
||||
return &conformance.FakeBackend{
|
||||
Desc: contract.BackendDescriptor{
|
||||
Name: "fake",
|
||||
Capabilities: []contract.Capability{contract.CapText},
|
||||
},
|
||||
Result: contract.Result{
|
||||
Outcome: contract.OutcomeSucceeded,
|
||||
Text: "ok",
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestFakeCancelSemantics(t *testing.T) {
|
||||
block := make(chan struct{})
|
||||
b := &conformance.FakeBackend{
|
||||
Desc: contract.BackendDescriptor{Name: "fake", Capabilities: []contract.Capability{contract.CapCancel}},
|
||||
BlockUntil: block,
|
||||
}
|
||||
conformance.Run(t, conformance.Case{
|
||||
Name: "cancel",
|
||||
Backend: b,
|
||||
Request: contract.Request{ExecutionID: "e-cancel"},
|
||||
CancelAfter: 50_000_000, // 50ms in ns (avoid time import here)
|
||||
WantOutcome: contract.OutcomeCanceled,
|
||||
WantErrCode: contract.ErrCanceled,
|
||||
})
|
||||
if !b.CanceledWithCtx {
|
||||
t.Fatal("backend must observe ctx cancellation before returning")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user