# 内部端口、事件与插件协议 ## 1. 原则 模块化单体内部不为每个模块创建 HTTP。领域模块通过类型化 Port、Command、Query 和 Domain Event 交互;只有独立扩缩容或安全边界才使用私有网络协议。 ```mermaid flowchart LR API[Native API] --> CMD[Application Commands] CMD --> DOMAIN[Domain Modules] DOMAIN --> OUTBOX[(Transactional Outbox)] OUTBOX --> WORKER[Workers] DOMAIN --> PORTS[Provider and Tool Ports] PORTS --> MODEL[Model Adapters] PORTS --> PLUGIN[Plugin RPC] PORTS --> SANDBOX[Sandbox RPC] ``` ## 2. 应用 Command 核心 Command 示例: ```text CreateCharacter CreateConversation AppendConversationMessageAndStartRun StartTaskRun SteerRun CancelRun ResumeRun DecideApproval CreateMemoryCandidate DecideMemoryCandidate PublishRelease InstallPlugin GrantDeviceCapability CreateOrder ApplyPaymentEvent CreateRefund CreateSettlement DecideModerationCase ``` 每个 Command 包含 `command_id`、actor、scope、trace context、idempotency key、occurred_at 和类型化 payload。Handler 在一个事务中写权威状态、AuditEvent 和 OutboxEvent。 ## 3. Query Port Query 返回授权后的 DTO,不返回 ORM Entity 或裸数据库连接。调用方必须提供 actor/scope;Repository 自身再次施加数据域条件,避免只在 API 层授权。 高成本检索使用显式 Query:`SearchMemories`、`SearchCommunity`、`BuildContextSnapshot`、`CalculateOrderQuote`。交易报价必须返回 price version 和 expires_at。 ## 4. Domain Event 与 Outbox Domain Event/Outbox 记录已提交领域事实,使用 `payload` 和聚合版本;公共 SSE 使用 [HTTP 与事件契约](http-and-events.md#4-sse) 的 `data`、Run `sequence` 和公开事件名;Normalized Model Event 只存在于 Model Adapter→Runtime 流中。Runtime 必须显式完成三层映射,不把 Provider/Normalized 事件原样作为公共事件或领域事实持久化。 Event envelope: ```json { "id": "event_01...", "type": "run.queued", "aggregate_type": "run", "aggregate_id": "run_01...", "aggregate_version": 1, "personal_domain_id": "domain_01...", "trace_id": "trace_01...", "occurred_at": "2026-09-03T08:00:00Z", "payload": {} } ``` 消费者按 event ID 幂等。事件只陈述已提交事实,不使用 `*.requested` 假装命令。命令需要结果时调用 Application Port;异步请求创建 Job/Run 资源。 ## 5. Normalized Model Port ```text ModelGateway.invoke(NormalizedRequest) -> AsyncIterator ModelGateway.count_tokens(NormalizedRequest) -> TokenCount ModelGateway.list_models(scope) -> ModelCapabilities[] ``` `NormalizedRequest` 包含逻辑模型、标准 content blocks、tools、tool choice、output contract、thinking/effort hint、cache plan、预算、地区、隐私级别和 trace。它不包含 Provider SDK 类型。 `NormalizedEvent`: ```text response.started content.block.started text.delta thinking.summary.delta tool.call.delta tool.call.completed usage.updated response.completed response.failed ``` Adapter 负责协议转换、Provider Header allowlist、错误分类、上游 request ID、usage 标准化和能力校验;Gateway Kernel 负责路由、重试边界、预算、ModelCall 持久化和首个可见事件提交。Runtime 将 `text.delta`、`tool.call.*` 等 Normalized Event 映射为统一 ContentBlock 与 `content.block.*` 公共 SSE;`response.*` 只控制内部调用生命周期,不直接进入公共事件表。 ## 6. Tool Runtime Port ```text ToolRegistry.snapshot(scope) -> ToolSnapshot Policy.evaluate(ToolInvocation, ActorContext) -> PolicyDecision ToolExecutor.execute(ExecutionTicket) -> ToolResult ``` ExecutionTicket 是短期、单 ToolCall、不可扩大权限的凭证,包含 tool/plugin version、参数 hash、文件/网络/设备范围、超时、资源限制、approval 和 idempotency key。Executor 不接受模型直接构造的票据。 ToolResult 包含 status、typed content、is_error、result size、evidence refs 和 redaction metadata。大结果落 File/Object,模型只收到摘要和引用。 ## 7. Worker 与 Lease 异步 Job 具有 `queued/running/succeeded/failed/cancelled` 状态。Worker 通过私有 Queue Port: ```text claim(queue, worker_id, lease_ms) -> JobLease | null heartbeat(job_id, lease_token) complete(job_id, lease_token, result_ref) fail(job_id, lease_token, retry_class, error) ``` Lease token 只在 Header/消息元数据传递,不能成为业务 API 凭据。过期 Lease 可重新领取;副作用步骤仍依赖业务幂等键,不能仅靠 Lease 防重复。 ## 8. Hunjing Plugin Protocol 通用插件协议采用 JSON-RPC 2.0: - 本地隔离插件:stdio; - 远程插件:HTTPS; - Tool-only 插件优先使用 MCP; - UI 扩展使用声明式 manifest + 隔离消息协议。 握手方法: ```text plugin.initialize plugin.get_manifest plugin.list_contributions plugin.activate plugin.deactivate plugin.health ``` 调用方法: ```text tool.invoke hook.observe hook.transform context.provide memory.extract_candidates media.process importer.inspect importer.apply ``` 每次请求带 `plugin_run_id`、PluginVersion、deadline、trace context 和短期 capability token。Host 验证响应 Schema、大小、时限和调用顺序。插件不能返回新权限,也不能调用未在 Manifest 声明的 Host service。 ### 8.1 Host 回调 插件需要访问 Host 时,只能调用受限反向 RPC: ```text host.files.read host.objects.create host.http.fetch host.secrets.use_reference host.events.publish_observation ``` Host 根据 capability token 决定范围。`secrets.use_reference` 只让代理侧在允许 host/位置注入秘密,不向插件返回明文。 ## 9. Sandbox RPC Sandbox Worker 处于独立安全边界,私有协议至少支持: ```text sandbox.start sandbox.execute sandbox.read_output sandbox.cancel sandbox.stop ``` 请求包含镜像摘要、只读/可写挂载、网络 allowlist、环境变量引用、CPU/内存/进程/时间限制和 ExecutionTicket。没有满足策略的后端时返回 `sandbox_unavailable`,禁止未隔离执行。 ## 10. 支付内部端口 ```text PaymentProvider.create_session(OrderSnapshot) PaymentProvider.verify_webhook(raw_body, headers) PaymentProvider.refund(PaymentReference, Amount) PayoutProvider.create_payout(SettlementSnapshot) ReconciliationProvider.list_transactions(period) ``` Provider Adapter 不写 Ledger。交易 Application Service 在验签和幂等后写 PaymentAttempt、LedgerEntry、Entitlement、Refund 或 PayoutAttempt。所有金额与货币由权威 Order/Settlement Snapshot 提供。 ## 11. 内部认证 独立服务使用 workload identity/mTLS 或短期服务令牌;不复用用户 Cookie、Service Account API key 或 DiagnosticToken。每个服务身份只获所需方法与数据域。内部 Header 同样传播 `traceparent` 和 `Hunjing-Request-ID`,但不把 `baggage` 当授权。