Skip to content

[codex] Add tool-driven Aevatar core invocation sources#830

Merged
eanzhao merged 192 commits into
devfrom
feature/core-loop
May 26, 2026
Merged

[codex] Add tool-driven Aevatar core invocation sources#830
eanzhao merged 192 commits into
devfrom
feature/core-loop

Conversation

@eanzhao
Copy link
Copy Markdown
Contributor

@eanzhao eanzhao commented May 22, 2026

背景

这个 PR 是 ADR-0026 的 Stage 1:把 aevatar 的核心能力重新定位为 LLM tool source,让模型通过 function call 主动选择何时使用 workflow、GAgent、team、readmodel observation 等能力,而不是继续在入口层维护 ForwardToGAgent / ForwardToTeam 这类并行路由方言。

改动

  • 新增 ADR:docs/adr/0026-tool-first-chat-ingress.md,明确 tool-first chat ingress 的目标、边界和后续阶段。
  • 新增 Aevatar.AI.ToolProviders.AevatarInvocation,提供 5 个 invocation tools:
    • aevatar_invoke_gagent
    • aevatar_invoke_team
    • aevatar_start_workflow
    • aevatar_observe_run
    • aevatar_query_readmodel
  • 新增共享 AevatarInvocationDispatcher,统一做 proto 参数解析、caller scope 注入、调度、readmodel 查询与结构化错误返回。
  • 通过 proto descriptor 生成严格 JSON schema,避免把核心语义塞进无约束 JSON bag。
  • 接入 Mainnet Host DI,让这些 tool sources 能进入现有 IAgentToolSource 发现链路。
  • 补 Lark caller-scope 回归测试,证明 Lark send tool 使用可信的 AgentToolRequestContext.NyxIdAccessToken,payload/外部 metadata 不能覆盖调用者凭据。
  • /v1/responses E2E 测试,证明模型发出的 aevatar_invoke_gagent additive tool call 会走 tool loop 并通过 IActorDispatchPort 投递 actor envelope,而不是走 legacy ForwardToGAgent 静态调用链路。

影响

  • 这是 tool-driven core loop 的第一步,不删除现有 legacy forward path。
  • GAgent / workflow 的 wait=complete 仍返回结构化 wait_complete_unavailable;当前阶段支持 ack / stream,后续由 session actor/观察链路承接长任务 continuation。
  • aevatar_query_readmodel 只允许查询封闭集合 readmodel,不开放任意 document collection。
  • 没有修改 NyxID、chrono-* 等外部仓库。

验证

  • dotnet test test/Aevatar.AI.ToolProviders.AevatarInvocation.Tests/Aevatar.AI.ToolProviders.AevatarInvocation.Tests.csproj --nologo:通过,21 passed。
  • dotnet test test/Aevatar.AI.ToolProviders.Lark.Tests/Aevatar.AI.ToolProviders.Lark.Tests.csproj --nologo:通过,61 passed。
  • dotnet test test/Aevatar.Hosting.Tests/Aevatar.Hosting.Tests.csproj --filter FullyQualifiedName~PostResponses_StreamWithAevatarInvokeGAgentAdditiveTool_ShouldDispatchActorEnvelope --nologo:通过,1 passed。
  • bash tools/ci/test_stability_guards.sh:通过。
  • bash tools/ci/architecture_guards.sh:通过。
  • git diff --check origin/dev..HEAD:通过。

备注:本地测试仍有既有 NuGet source mapping / analyzer warnings,没有测试失败。

loning and others added 22 commits May 22, 2026 13:32
…pology API,保留 typed scripting ports (#802)

违反 CLAUDE.md "Runtime 负责 lifecycle/topology/lookup,Dispatch Port 负责投递;禁止揉成全能接口"。

变更:
- 删 `IScriptBehaviorRuntimeCapabilities` 4 个 raw lifecycle 方法
  (CreateAgentAsync/DestroyAgentAsync/LinkAgentsAsync/UnlinkAgentAsync)
- `ScriptBehaviorRuntimeCapabilities` / `Factory` 移除 `IActorRuntime` 依赖
- 测试/integration fixtures 改用 typed scripting ports
  (provisioning/command/definition/catalog/evolution)
- docs/canon/scripting.md 同步更新

Verification: 388 scripting tests pass, architecture+test_stability guards green.
LOC: -253 net.

Closes #799
⟦AI:AUTO-LOOP⟧

Co-authored-by: codex-refactor-loop <noreply@aevatar>
)

* refactor(iter27 cluster-027): 删 SkillRegistry + TTL 缓存,新建 local-only LocalSkillCatalog

违反 CLAUDE.md "权威状态" + "中间层状态约束":SkillRegistry 用 5min 进程内 TTL 缓存 remote skill,跨用户 token 共享。

变更:
- 删 SkillRegistry / SkillRegistryTtlTests / 5min cache
- 新建 LocalSkillCatalog(local-only,无 TimeProvider/FetchedAt/TTL/_lock)
- UseSkillTool: remote skill 改 per-request 调 IRemoteSkillFetcher.FetchSkillAsync(currentToken),不缓存
- ConversationReplyGenerator / SkillsAgentToolSource / DI 同步
- docs/canon/daily-command-pipeline.md factual sync(删 5min cache 措辞)
- SCOPE_EXTEND: ConversationReplyGeneratorTests 改 LocalSkillCatalog 引用

Verification: 18 ornn tests pass, architecture+docs lint guards green.
LOC: -39 net production / -56 net tests.

Closes #796
⟦AI:AUTO-LOOP⟧

Co-Authored-By: codex-refactor-loop <noreply@aevatar>

* fix(PR #803 round-1): 加 bulk RegisterRange test + 源码回归 guard (tests reject 修)

⟦AI:AUTO-LOOP⟧

---------

Co-authored-by: codex-refactor-loop <noreply@aevatar>
… repair_lark_mirror,保留 register_lark_via_nyx + rebuild_projection (#805)

违反 CLAUDE.md "读写分离"(ChannelBotRegistrationScopeBackfill 从 readmodel 反推 write candidate)+ "命名/架构" maintainer 反馈(repair_lark_mirror / backfill 暴露技术词,业务语义不清)。

变更(reflector#2 force-pick "delete-repair-surface"):
- 删 ChannelBotRegistrationScopeBackfill / NyxRelayApiKeyOwnershipVerifier / RepairLocalMirrorAsync
- 删 live route POST /api/channels/registrations/repair-lark-mirror + tool action repair_lark_mirror
- rebuild_projection 改 projection-only(不读 readmodel,不调 backfill)
- 保留 ChannelBotScopeIdRepairedEvent replay(committed event 兼容)
- 删 backfill/repair specific tests; docs/operations runbook 简化
- prompt config / system-prompt 同步:stale readmodel → rebuild_projection;missing local mirror → register_lark_via_nyx

Verification: 789 ChannelRuntime tests pass, architecture+docs lint guards green。
LOC: -2651 net production+tests。

Closes #779
⟦AI:AUTO-LOOP⟧

Co-authored-by: codex-refactor-loop <noreply@aevatar>
…ojection waits (#804)

* refactor(iter27 cluster-028): Identity OAuth endpoint 改 CQRS dispatch + 删 projection 等待

违反 CLAUDE.md "ACK 诚实" + "query-time priming forbidden":endpoint/bootstrap 直接构造 EventEnvelope 投递并在请求路径同步等 projection readiness/rebuild observation/readmodel polling。

变更:
- 加 ChannelIdentityOAuthCommandDispatch + typed CQRS adapters (CommitExternalIdentityBinding/ObserveBrokerCapability/ProvisionOAuthClient/EnsureProvisioned)
- IdentityOAuthEndpoints/AevatarOAuthClientBootstrapService inject ICommandDispatchService<...>,返回 accepted/pending + status URL
- 删 IProjectionReadinessPort / ExternalIdentityBindingProjectionPort / AevatarOAuthClientProjectionPort / AevatarOAuthClientRebuildCoordinator / ProjectionWaitTimeout / WaitForRebuildObservedAsync
- ChannelIdentityCommittedStateProjectionActivationPlanProvider for committed-state materialization
- docs/adr/0018: 同步 accepted/pending 描述
- tools/ci/query_projection_priming_guard.sh: 加 Identity OAuth path 检查
- 测试同步:删 ExternalIdentityBindingProjectionReadinessPortTests + 新 ChannelIdentityOAuthCommandDispatchTests/ActivationPlanProviderTests/BootstrapServiceTests

Verification: 808 ChannelRuntime tests pass, architecture+query_projection_priming guards green。
LOC: -974 net (-1457 / +483)。

Closes #797
⟦AI:AUTO-LOOP⟧

Co-Authored-By: codex-refactor-loop <noreply@aevatar>

* fix(PR #804 round-1): 加 readiness port / coordinator polling regression tests + assert no projection wait

⟦AI:AUTO-LOOP⟧

* fix(PR #804 round-2): 调整 CQRS dispatch 注册 + bootstrap service + composition test

⟦AI:AUTO-LOOP⟧

* fix(PR #804 round-3): callback accepted 加 status_url + test 同步 assert

⟦AI:AUTO-LOOP⟧

* fix(PR #804 round-4): revert ADR 0018 改动 + 新建 ADR 0024 supersede (architect ADR immutability reject 修)

⟦AI:AUTO-LOOP⟧

---------

Co-authored-by: codex-refactor-loop <noreply@aevatar>
Records the architectural decision to collapse ChatRouteAction to
Reject + ForwardToModel, exposing GAgent/Team/Workflow invocation
as IAgentToolSource tools through the existing ToolCallLoop. Supersedes
ADR-0024 §D5 (v1 action set) and ADR-0025 (voice v1 ForwardToGAgent);
ADR-0024 D1/D2/D3/D4/D6 stand.

Tracked end-to-end in epic #808; voice GA prerequisite
in #809.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lifecycle (#814)

* refactor(iter30 cluster-030): 删 workflow step raw actor lifecycle,改 role-level agent_kind + WorkflowRunGAgent

违反 CLAUDE.md "actor lifecycle 不暴露调用方" + workflow step parameter 暴露 raw CLR Type.GetType + AppDomain scan + actor create/link。

变更(consensus structural):
- Foundation: IActorRuntime 加 CreateByKindAsync(local + Orleans 双 runtime)
- Workflow: RoleDefinition.AgentKind 字段;WorkflowRunGAgent 用 CreateByKindAsync 创角色 actor
- WorkflowStepTargetAgentResolver 删 agent_type/agent_id/Type.GetType/AppDomain scan/IRoleAgentTypeResolver/IWorkflowAgentTypeAliasProvider raw lifecycle
- Bridge: TelegramBridge/UserBridge/WaitReply 加 [GAgent("workflow.telegram-*")] stable kind token
- Validator: reject parameters.agent_type/agent_id
- CLI workflows: target_role + agent_kind 替代 agent_type
- docs/canon: workflow-primitives/role-model/workflow-runtime 同步(不动 architecture-vocabulary.md)

Verification: 239 workflow tests pass, architecture+docs lint guards green.
LOC: -603 net(28 files +341 / -944)。

Closes #811
⟦AI:AUTO-LOOP⟧

Co-Authored-By: codex-refactor-loop <noreply@aevatar>

* fix(PR #814 round-1): 加 WorkflowRunGAgent+AgentKind tests + parser/regression tests (tests reject 修)

⟦AI:AUTO-LOOP⟧

* fix(PR #814 round-2): 加 OrleansActorRuntime.CreateByKindAsync forwarding tests

⟦AI:AUTO-LOOP⟧

* fix(PR #814 round-3): LocalActorRuntime.CreateByKindAsync branch coverage (existing/mismatch/concurrent/cleanup)

⟦AI:AUTO-LOOP⟧

* fix(PR #814 round-4): 加 AddWorkflowBridgeExtensions kind 注册 behavior test

⟦AI:AUTO-LOOP⟧

---------

Co-authored-by: codex-refactor-loop <noreply@aevatar>
… bug)

PR #804 merge 时未 check ADR 编号唯一性,trunk 上 0024 与 0024-chat-route-policy.md 重复,docs lint 挂。

⟦AI:AUTO-LOOP⟧
…eam + delete watchdog race (#815)

* refactor(iter26 cluster-030): Telegram /getUpdates 改 actor-owned ExternalLink stream

reflector#2 force-pick existing-externallink-stream(Auric narrowing: "这跟 actor 持有 stream 是一类问题吧?")。

违反 CLAUDE.md "延迟/超时事件化" + "跨 actor 等待 continuation 化":TelegramBridgeGAgent.ExecuteConnectorWithWatchdogAsync 用 Task.Delay 兜底超时 + ContinueWith race + actor turn 内同步 await /getUpdates 长轮询。

变更:
- TelegramWaitReplyGAgent 实现 IExternalLinkAware,声明 stable Telegram get-updates link
- 加 TelegramGetUpdatesExternalLinkTransport(transport 跑 /getUpdates 在 actor turn 外)
- /getUpdates 走 IExternalLinkPort.SendAsync;result 经 ExternalLinkMessageReceivedEvent 回 actor inbox
- 删 ExecuteConnectorWithWatchdogAsync + ResolveConnectorExecutionWatchdogMs + Task.Delay/ContinueWith race
- telegram_wait_reply.proto: 加 TelegramGetUpdatesPollRequest/Result
- SCOPE_EXTEND: test/Aevatar.Workflow.Host.Api.Tests/TelegramBridgeGAgentTests.cs(existing Telegram bridge tests 在此 host api project)

无新 actor type(reuse 已 ExternalLink pattern,Auric narrowing 已明示)。

Verification: 334 Host.Api tests pass, build green.
LOC: +505 / -240(包括 transport + proto + tests)。

Closes #800
⟦AI:AUTO-LOOP⟧

Co-Authored-By: codex-refactor-loop <noreply@aevatar>

* fix(PR #815 round-1): actor-owned poll timeout continuation + transport/result-handling branch tests

- TelegramWaitReplyGAgent: 加 poll timeout self-event(hung /getUpdates 不再永远等)
- transport / result-handling branch tests
- telegram_wait_reply.proto: 加 TelegramGetUpdatesPollTimeout event

⟦AI:AUTO-LOOP⟧

* fix(PR #815 round-2): 加 TelegramGetUpdatesExternalLinkTransport DI registration test

⟦AI:AUTO-LOOP⟧

* fix(PR #815 round-3): proto 替代 JSON string actor-state (architect Protobuf serialization 修)

⟦AI:AUTO-LOOP⟧

* fix(PR #815 round-4): 加 stale timeout branch regression test + RequestId guard (tests reject 修)

⟦AI:AUTO-LOOP⟧

---------

Co-authored-by: codex-refactor-loop <noreply@aevatar>
…am (#817)

* refactor(iter31 cluster-032): ChatRuntime 删 TaskRun + Channel,actor turn 自己 own stream

违反 CLAUDE.md "回调只发信号" + "actor execution integrity":ChatRuntime.ChatStreamAsync 用 Task.Run + Channel/ChannelWriter 在 actor turn 外跑 LLM/tool/hook/history 业务循环。

reflector#1 force-pick "delete-owned-stream-private-bridge"(4 round split stalled,reflector 拍板)。

变更:
- 删 Task.Run + Channel<LLMStreamChunk> + ChannelWriter + _streamBufferCapacity 整个 owned stream 框架
- ChatStreamAsync 自己 own stream flow(actor turn 内 IAsyncEnumerable)
- 删 stream_buffer_capacity proto field / YAML schema / Configuration property
- Reserve proto field number(向后兼容旧 binary)
- middleware bridge: 改 private/internal adapter-only,无 public stream middleware 接口
- docs/canon/workflow-primitives.md / workflow-runtime.md: 删 stale stream_buffer_capacity 例
- SCOPE_EXTEND: tests/integration 同步删 contract assertions

Verification: 68 AI Core tests pass, architecture+docs lint guards green。
LOC: -44 net(28 files +460 / -504)。

Closes #816
⟦AI:AUTO-LOOP⟧

Co-Authored-By: codex-refactor-loop <noreply@aevatar>

* fix(PR #817 round-1): ChatStreamAsync 流式 yield + 源码 regression test (architect: real streaming;tests: source-regression)

⟦AI:AUTO-LOOP⟧

* fix(PR #817 round-2): 加 normal await next() middleware path + workflow compatibility tests

⟦AI:AUTO-LOOP⟧

---------

Co-authored-by: codex-refactor-loop <noreply@aevatar>
…ng (#819)

* refactor(iter32 cluster-034): chat route policy 删 request-path projection priming + 加 committed-state hook

违反 CLAUDE.md "禁止 query-time priming" + "命令骨架内聚":ChatRoutePolicyAdminEndpoints + VoiceDemoBootstrap 在 request path 调 EnsureProjectionForActorAsync 同步 priming。

变更(consensus minimal):
- 新 ChatRoutePolicyCommittedStateProjectionActivationPlanProvider(committed-state hook 触发,匹配 ChatRoutePolicyUpdated)
- 删 ChatRoutePolicyProjectionPort + EnsureProjectionForActorAsync request-path 调用
- DI 注册 ProjectionActivationPlanDispatcher + CommittedStateProjectionActivationHook + 新 provider
- query_projection_priming_guard 加 ChatRoutePolicyAdminEndpoints / VoiceDemoBootstrapEndpoints 扫描
- 新 tests:provider tests / DI registration tests;modify endpoint tests removing port fixture

Verification: 17 ChatRouting tests pass, architecture+priming guards green。
LOC: +166/-86 net +80。

Closes #818
⟦AI:AUTO-LOOP⟧

Co-Authored-By: codex-refactor-loop <noreply@aevatar>

* fix(PR #819 round-1): 加 voice demo bootstrap behavior test + source regression assertion

⟦AI:AUTO-LOOP⟧

---------

Co-authored-by: codex-refactor-loop <noreply@aevatar>
Implements ADR-0026 Stage 1 unit-1 (epic #808). New project
src/Aevatar.AI.ToolProviders.AevatarInvocation/ exposes aevatar_invoke_gagent /
_invoke_team / _start_workflow / _observe_run / _query_readmodel as
IAgentToolSource, so the LLM can drive orchestration through the existing
ToolCallLoop instead of parallel router branches.

Design:
- Tool payloads are proto-derived strict JSON-Schema (no map<string,string> bags)
- wait=ack|stream|complete supported; stream is default for long-running tools;
  GAgent/workflow wait=complete returns wait_complete_unavailable until Stage 2
  session actor lands
- Caller scope flows through AgentToolRequestContext only; protected caller-scope
  keys (LLMRequestMetadataKeys.*) are stripped from LLM-supplied payload.headers
  before server values are stamped, so the LLM cannot inject overrides for
  nyxid.access_token / scope_id / owner_subject etc.
- query_readmodel is bounded to a closed registered set
- Dispatch reuses existing surfaces (IActorDispatchPort,
  ITeamEntryMemberResolver + IStaticGAgentStreamInvocationPort<AGUIEvent>,
  ICommandDispatchService<WorkflowChatRunRequest,...>); no new dispatch chain

21 tests pass (4 credential-injection regression + 1 ObserveRun fast-fail
added in post-review hardening); arch_guards + test_stability + docs lint all
PASS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements ADR-0026 Stage 1 unit-2 (D7 prerequisite) for the Lark
outbound caller-scope guarantee. After auditing the existing path
(LarkMessagesSendTool → LarkNyxClient → NyxIdApiClient) no production
refactor was required: the tool already reads
AgentToolRequestContext.NyxIdAccessToken (no credential parameters) and
forwards the caller bearer through NyxID's api-lark-bot proxy, which
exchanges to a Lark tenant_access_token without seeing the caller's
authorization header. The metadata-bag credential-injection surface that
unit-1 had to harden is structurally absent here (no headers/metadata
bag at the dispatch boundary).

Added 2 regression tests:
- Asserts the dispatched NyxID call carries AgentToolRequestContext's
  trusted typed NyxIdAccessToken
- Asserts a malicious LLM payload (smuggled nyx_id_access_token, fake
  headers, ExternalMetadata overriding LLMRequestMetadataKeys.NyxIdAccessToken)
  cannot override the trusted caller token at dispatch

NyxID investigation summary (verified via gh against ChronoAIProject/NyxID
backend source): /api/v1/proxy/s/api-lark-bot/open-apis/im/v1/messages
accepts only the caller's NyxID bearer; NyxID resolves caller's
api-lark-bot binding, exchanges {app_id, app_secret} → tenant_access_token
per channel_adapters/lark.rs::lark_family_token_exchange_config, strips
the inbound authorization, and injects bearer for outbound to Lark.
Semantic: messages post as the caller's bound Lark bot (NyxID-mediated),
not as the human user's OAuth identity and not as Aevatar's service-level
identity. This satisfies ADR-0026 §D7's "lands in the caller's Lark
account" use case.

61/61 tests pass; arch_guards + test_stability + docs lint all PASS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(iter33 cluster-035): 删 workflow IActorRuntime topology side-read,改 committed-event projection

违反 CLAUDE.md "运行时形态不是业务事实" + "禁止 side-read":Workflow report 用 IActorRuntime.GetAsync().GetChildrenIdsAsync() 读 runtime children 当 topology 事实。

变更(consensus structural):
- 删 IWorkflowExecutionTopologyResolver + ActorRuntimeWorkflowExecutionTopologyResolver
- 删 Application DI 注册 + 资源 lookup
- topology 由现有 committed event projection 接管(WorkflowRoleActorLinkedEvent + SubWorkflowBindingUpsertedEvent)
- WorkflowExecutionTopologySource 枚举:RuntimeSnapshot → CommittedProjection
- WorkflowExecutionArtifactMaterializationSupport 标记 source = CommittedProjection
- tests:删除 banned BFS resolver test;assertions 更新到新枚举

Verification: 179 Application tests pass, architecture+docs lint guards green。
LOC: +30/-199 net -169。

Closes #820
⟦AI:AUTO-LOOP⟧

Co-Authored-By: codex-refactor-loop <noreply@aevatar>

* fix(PR #821 round-1): 加 source-regression test 锁定 no IActorRuntime.GetChildrenIdsAsync 重入

⟦AI:AUTO-LOOP⟧

---------

Co-authored-by: codex-refactor-loop <noreply@aevatar>
…#822)

* iter29 cluster-029: workflow history/report/graph artifact 边界 cleanup

错误模式: workflow history/report/graph 被当成 actor current-state readmodel
(current-state query 读 report artifact 来 enrich actor snapshot;
WorkflowRunTimelineDocument / WorkflowRunGraphArtifactDocument 仅复制
WorkflowRunInsightReportDocument;public surface 暴露为 actor current-state query)

新原则: workflow history/report/graph 是 workflow-run artifact (或 aggregate-owned view),
不是 actor current-state readmodel。WorkflowExecutionCurrentStateDocument 仍是唯一
workflow actor-scoped current-state readmodel。删重复 timeline/graph shell。停止
current-state query path 读 report artifact。重命名 application/query/tool/HTTP
surface 为 workflow-run artifact/export 语义。

不引入 CLAUDE 例外 / 新 generic storage seam / 新 actor type。

Closes #810
⟦AI:AUTO-LOOP⟧

* iter29 cluster-029 fix r1: 补 8 处 reviewer demand

- 补 ChatQueryEndpointsTests 验证 workflow-run artifact/export surface 重命名
- 补 AevatarWorkflowClientTests 验证 SDK 客户端 stub 同步更新
- WorkflowExecutionQueryModels / WorkflowProjectionQueryModels.Partial 细化语义
- EventQueryTool / WorkflowStatusTool tool descriptions align artifact 语义
- ChatQueryEndpoints / WorkflowExecutionReadModelMapper 校正字段映射

⟦AI:AUTO-LOOP⟧

* iter29 cluster-029 fix r2: 补齐 mapped-route 行为测试

- 加 mapped-route integration test 真发 HTTP request 到
  /api/workflow-runs/{id}/timeline-export / graph-export/edges /
  graph-export/subgraph / graph-export/enriched
- 加 SDK GetWorkflowRunTimelineExportAsync test 验证 URL / take /
  response handling
- tool 合约注释 + WorkflowProjectionQueryModels.Partial 细化

⟦AI:AUTO-LOOP⟧
* iter33 cluster-claude-md-slim: CLAUDE.md 379 → 151 行(单文件瘦身 60%)

错误模式: CLAUDE.md 单文件 379 行 / 22 段,混合顶级架构约束(核心)
+ 操作类细则(Codex CLI 调用规范 / gstack / 文档系统 / 项目结构
/ 构建与运行 / 编码风格 / 前端设计 / 测试门禁 / 提交与 PR /
文档 mermaid 等)+ 重复条款。

新原则: CLAUDE.md 保持单文件但瘦身到 ~150 行,保留真核心:
- 顶级架构约束
- 架构哲学
- 字段命名与 Metadata 决策树
- Command / Envelope / Dispatch
- 权威状态 / ReadModel / Projection
- Actor 设计原则 / 生命周期 / 执行模型
- 中间层状态约束
- 序列化

非顶级条款删除或移到 skill / docs:
- Codex CLI 调用规范 → 已在 codex-refactor-loop skill 自包含
- gstack → 已在 gstack skill 自包含
- 前端设计 → 已在 aevatar-frontend-design skill 自包含
- 项目结构 / 构建运行 / 编码风格 / 文档系统 / 测试门禁 / 提交PR
  → 删除细则,保留 essential 引用

skill 死链清理:codex-refactor-loop + codex-implement-loop SKILL.md
中指向被删 CLAUDE.md 段的死链改为指向 skill 内部 section。

不拆 CLAUDE.md 为 docs/canon multi-file vocab。
不引入 tools/docs/lint.sh 新 guard(本 cluster scope 不含)。

Closes #801
⟦AI:AUTO-LOOP⟧

* iter33 cluster-claude-md-slim fix r1: 恢复 architect 强制条款 + 补 refactor self-doc

- architect: 恢复 publisher 直操禁令 / query-time replay 允许刷新路径 /
  Application 层禁 IXxxStore 直接 endpoint 依赖 IActorRuntime
  / IProjectionDocumentReader 等强制条款(瘦身保留 normative force)
- quality: 加 refactor self-doc Old/New block 注明 cluster id + 边界

⟦AI:AUTO-LOOP⟧

* iter33 cluster-claude-md-slim fix r2: 恢复 docs governance 强制条款

architect r2 reject demand: canon/ADR YAML frontmatter mandatory clause + tools/docs/lint.sh enforcement 在瘦身后丢失。补回 normative force(短句)。

⟦AI:AUTO-LOOP⟧
)

错误模式: projection session event transport 同时携带 protobuf bytes
和 legacy string payload 兼容路径(proto field + codec compat interface
+ legacy payload write path);Projection.Core legacy codec 表面未被
任何 concrete codec 实现。

新原则: 仅保留 protobuf payload bytes 路径;删除 legacy codec
compatibility interface(确认无 implementation 依赖);proto
legacy_payload string field reserved per protobuf evolution rules;
ProjectionSessionEventHub 同步删 legacy write path;相关 tests/docs
对齐更新。

Closes #N/A (audit-direct cluster, requires_design=false)
⟦AI:AUTO-LOOP⟧
Closes ADR-0026 Stage 1 (epic #808). Integration test
demonstrates the new tool-first ingress path works end-to-end after
units 1+2 landed, without touching any production code.

Test: MainnetResponsesEndpointsTests.PostResponses_StreamWithAevatarInvokeGAgentAdditiveTool_ShouldDispatchActorEnvelope

Scenario:
- /v1/responses streamed request with real DI registration of unit-1's
  AddAevatarInvocationTools (5 production IAgentToolSource instances)
- Stubbed LLM emits aevatar_invoke_gagent tool call with a malicious
  payload that smuggles nyxid.access_token + aevatar.scope_id overrides
- ResponsesCompletionApplicationService executes the local tool call
  inline (not as function_call SSE output — verified against production
  StreamAsync behavior)
- AevatarInvocationDispatcher dispatches through IActorDispatchPort
  (captured by RecordingActorDispatchPort)
- LLM round 2 continues after tool result, SSE lifecycle completes

Assertions:
- Dispatched envelope's Route.PublisherActorId == DirectGAgentPublisherId
- Dispatched ChatRequestEvent.Headers carry the trusted bearer/scope
  (caller-scope protection from unit-1 verified end-to-end)
- ThrowingStaticGAgentStreamInvocationPort.InvocationCount == 0
  (the legacy ForwardToGAgent/ForwardToTeam path in
  ResponsesEndpoints.cs:779-927 is NOT entered)

202/202 tests pass in Aevatar.Hosting.Tests; arch_guards +
test_stability_guards + docs lint all PASS. No production code changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@eanzhao eanzhao marked this pull request as ready for review May 22, 2026 17:19
@codecov
Copy link
Copy Markdown

codecov Bot commented May 22, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.81%. Comparing base (9ad361d) to head (c992595).

@@            Coverage Diff             @@
##              dev     #830      +/-   ##
==========================================
+ Coverage   82.52%   82.81%   +0.29%     
==========================================
  Files         981     1003      +22     
  Lines       61962    66302    +4340     
  Branches     8074     8574     +500     
==========================================
+ Hits        51133    54909    +3776     
- Misses       7374     7656     +282     
- Partials     3455     3737     +282     
Flag Coverage Δ
ci 82.81% <ø> (+0.29%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...Abstractions/Agents/RoleConfigurationNormalizer.cs 96.07% <ø> (-0.22%) ⬇️
....AI.Abstractions/LLMProviders/LLMControlContext.cs 97.43% <ø> (ø)
...stractions/LLMProviders/LLMControlContextMapper.cs 100.00% <ø> (ø)
...Aevatar.AI.Abstractions/LLMProviders/LLMRequest.cs 77.61% <ø> (+0.33%) ⬆️
...s/ToolProviders/AgentToolExecutionContextMapper.cs 97.87% <ø> (-1.20%) ⬇️
src/Aevatar.AI.Core/AIGAgentBase.cs 80.75% <ø> (-3.22%) ⬇️
src/Aevatar.AI.Core/Chat/ChatRuntime.cs 88.94% <ø> (-1.52%) ⬇️
...atar.AI.Core/LLMProviders/OwnerLlmConfigApplier.cs 75.75% <ø> (-6.39%) ⬇️
src/Aevatar.AI.Core/RoleGAgent.cs 82.95% <ø> (+1.14%) ⬆️
src/Aevatar.AI.Core/RoleGAgentFactory.cs 81.41% <ø> (-0.49%) ⬇️
... and 4 more

... and 360 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

loning added 3 commits May 23, 2026 01:40
…ts (#829)

* iter34 cluster-005: Mainnet Host endpoints 改走 Application command ports

错误模式: Mainnet Host endpoints (ChatRoutePolicyAdminEndpoints /
VoiceDemoBootstrapEndpoints) 直接 inject IActorRuntime /
IActorDispatchPort,build EventEnvelope + dispatch 在 Host 内做。

新原则: Host 仅调 Application 层 command port(IChatRoutePolicyCommandPort
/ IVoiceDemoAgentCommandPort),port normalize + resolve target + build
envelope + dispatch + return honest accepted receipt。Host endpoint 保
持最小(auth + body parsing)。

引入 IChatRoutePolicyCommandPort + ChatRoutePolicyCommandPort +
IVoiceDemoAgentCommandPort + VoiceDemoAgentCommandPort + DI 注册接线
+ endpoints/handlers 同步测试更新。

⟦AI:AUTO-LOOP⟧

* iter34 cluster-005 fix r1: 补 6 处 tests reviewer demand

- 补 ChatRoutePolicyCommandPort 行为单元 tests
- 补 VoiceDemoAgentCommandPort 测试覆盖
- 补 端点 mapped-route integration tests 覆盖 command port 调用

⟦AI:AUTO-LOOP⟧

* iter34 cluster-005 fix r2: 补 7 处 tests demand

- 补 ChatRoutePolicyCommandPort.UpsertAsync / RemoveRuleAsync null-guard tests
- 补 VoiceDemoAgentCommandPort 同类 null-guard tests
- 补 endpoint 行为测试覆盖空命令路径

⟦AI:AUTO-LOOP⟧
#831)

* iter34 cluster-006: service artifact projectors 改 state-root overwrite

错误模式: Service artifact projectors (ServiceCatalogProjector /
ServiceDeploymentCatalogProjector / ServiceRolloutProjector) 在投影
时读取 prior readmodel 文档进行增量 mutation,让 readmodel 成为第二
套状态机。

新原则: 仅做 monotonic state-root overwrite,不读 prior readmodel。
Service catalog readmodel 改为 definition-only(unique 服务定义/owner/
version),active deployment/serving 字段删除;facts 派生自 existing
deployment / serving readmodels。

不引入新 actor / 新 envelope / 新 projection phase;不动 CLAUDE.md /
docs/canon。

Closes #827
⟦AI:AUTO-LOOP⟧

* iter34 cluster-006 fix r1: 补 8 处 architect/tests demand

补 query reader / catalog query / proto 字段一致性 + 测试覆盖

⟦AI:AUTO-LOOP⟧

* iter34 cluster-006 fix r2: 补 5 处 tests demand 补全 projector + query 路径覆盖

⟦AI:AUTO-LOOP⟧

* iter34 cluster-006 fix r3: 移除 active deployment query-time composition,改读专属 readmodel

architect r3 reject: ServiceLifecycleQueryApplicationService 在 query-time 聚合 active deployment 违反 CLAUDE 聚合必须 actor 化 + 查询走 readmodel。改为:Snapshot 不再含 active 字段,consumer 直接查 ServiceDeploymentReadModel / ServingReadModel。

⟦AI:AUTO-LOOP⟧
… port (#832)

* iter34 cluster-004: voice bootstrap 改 Application/actor-owned command port

错误模式: VoiceDemoBootstrapEndpoints 在 Host 内同步等待 actor readiness
+ observation loop;POST 返回前阻塞读取 actor 状态;route mutation 在
Host 内做。

新原则(medium-B framing,reflector force-pick): 删 POST readiness
polling;移 voice bootstrap + voice-demo route mutation 到 Application/
actor-owned typed command port;POST 返回 honest accepted receipt;
readiness 由 client 显式 query readmodel(或事件 notification);
**无新 bootstrap actor / 新 envelope / 新 projection phase / mandatory
status endpoint / shared route-policy command port**。

不动 top-level CLAUDE.md / docs/canon。

Closes #826
⟦AI:AUTO-LOOP⟧

* iter34 cluster-004 fix r1: 补 8 处 architect/tests demand + 统一 command port 位置

- 移 VoiceDemoAgentCommandPort 到 agents/Aevatar.GAgents.NyxidChat/Voice/
  (与 cluster-005 unified location;cluster-005 已 merged)
- 补 endpoint integration tests 覆盖 accepted receipt 行为
- 补 command port behavior + null-guard tests

⟦AI:AUTO-LOOP⟧

* iter34 cluster-004 fix r2: 补 7 处 architect+tests demand

⟦AI:AUTO-LOOP⟧

* iter34 cluster-004 fix r3: 补 5 处 tests demand 完成 validation branch 测试

⟦AI:AUTO-LOOP⟧
loning and others added 29 commits May 25, 2026 03:27
…eam-entry fallback (#1001)

* iter92 cluster-793: split ChatRouteAction target semantics + remove default team-entry fallback (#793)

Phase 9 r1 consensus split-chat-route-target-semantics-and-remove-default-team-entry-fallback:
- ChatRouteAction adds typed ForwardToStudioMember (member_id + optional endpoint_id + scope override); ForwardToGAgent narrowed to direct actor/grain target only.
- /v1/responses + /v1/messages facades only accept ForwardToStudioMember and ForwardToTeam; ForwardToGAgent returns explicit route contract error.
- ADR-0024 updated; DefaultTeamEntryMemberResolver platform-level DI default removed (hosts must register StudioTeamEntryMemberResolver or fail-fast).
- Contract tests updated/added; transitional fallback test removed.

Closes #793

⟦AI:AUTO-LOOP⟧

* fix(pr1001/r2): add cluster self-doc + drop unused callerScope + cover ForwardToStudioMember endpoint/scope override

- chat_route_policy.proto + ResponsesCommandFacade + ResponsesForwardingApplicationService: add Refactor (iter92/cluster-793) Old/New self-doc; old = /v1/responses treated ForwardToGAgent.actor_id as Studio member id, new = ForwardToGAgent direct actor target only, Studio member routing via ForwardToStudioMember.
- ResponsesEndpoints.HandleForwardedAguiAsync: remove unused callerScope parameter and call sites.
- ResponsesCommandFacadeTests + MainnetResponsesEndpointsTests: extend ForwardToStudioMemberAction helper to accept endpointId + scopeId; add behavior tests asserting resolver receives override scope + member id, static invocation receives explicit endpoint, final ServiceIdentity.TenantId reflects resolver output.

⟦AI:AUTO-LOOP⟧
…1002)

* iter92 cluster-645: soft-deprecate StreamingProxy with Sunset header + docs + guard (#645)

Phase 9 r2 consensus soft-deprecate-streamingproxy-with-sunset-no-hard-delete:
- StreamingProxy Host route + SDK surface preserved (public contract, no in-repo client, doc claim)
- Added Deprecation + Sunset HTTP header (RFC 8594) pointing to /v1/responses
- README / docs/canon / ADR-0015 / streaming-proxy-flow doc marked deprecated with sunset date and migration path
- Architecture guard prevents new StreamingProxy consumers without explicit exception
- No code/proto/test deleted; backward compat preserved

Closes #645

⟦AI:AUTO-LOOP⟧

* fix(pr1002/r2): add Refactor (iter92/cluster-645) Old/New self-doc on StreamingProxy endpoints + guard

- agents/Aevatar.GAgents.StreamingProxy/StreamingProxyEndpoints.cs: Old/New self-doc above the deprecation header block; old = public streaming proxy entry, new = soft-deprecated compatibility-only with Sunset header
- tools/ci/architecture_guards.sh: Old/New self-doc above new guard; old = no block on adding StreamingProxy consumer, new = guard rejects new consumers, direct model streaming goes via /v1/responses

⟦AI:AUTO-LOOP⟧
… in production write paths (#1003)

* iter92 cluster-092: stop emitting deprecated OwnerScope legacy fields in production write paths (#1000)

audit-iter-92 cluster-092-owner-scope-deprecated-field-emission:
- SkillRunnerGAgent: stop setting deprecated Platform/OwnerNyxUserId on UserAgentCatalogUpsertCommand when OwnerScope is present.
- UserAgentCatalogGAgent: persist deprecated scattered fields only when OwnerScope is missing (legacy command apply path).
- UserAgentCatalogProjector: leave document deprecated fields empty for entries with OwnerScope.
- AgentDeliveryTargetTool: LLM rebind path no longer emits deprecated command fields.
- Preserve OwnerScope.FromLegacyFields + query-port fallback for old persisted rows.
- Tests updated to reflect new write semantics; legacy-row fallback path tests preserved.

Closes #1000

⟦AI:AUTO-LOOP⟧

* fix(pr1003/r2): add Refactor (iter92/cluster-092) self-doc + SkillRunner legacy fallback behavior test

- Self-doc blocks on UpsertRegistryAsync / ownership merge / projector cleanup / AgentDeliveryTargetTool emit point: old emitted deprecated Platform/OwnerNyxUserId always; new emits OwnerScope only, legacy fields kept only on missing-OwnerScope fallback path.
- SkillRunnerGAgentTests: add fallback-branch behavior test asserting UserAgentCatalogUpsertCommand still carries derived OwnerScope plus legacy Platform/OwnerNyxUserId when runner state lacks OwnerScope (backward compat).

⟦AI:AUTO-LOOP⟧
…payloads (#1005)

* iter93 cluster-093: type card action workflow resume + LLM selection control payloads (#1004)

Phase 9 r1 consensus type-card-action-workflow-resume-and-llm-selection-payloads:
- chat_activity.proto: add WorkflowResumeActionPayload + LlmSelectionActionPayload typed sub-messages on ActionElement / CardActionSubmission; arguments map demoted to open-extension-only.
- FeishuCardHumanInteractionPort + LarkMessageComposer + TelegramMessageComposer: emit typed payload, keep adapter JSON compat at composer boundary.
- NyxIdRelayTransport.BuildCardActionSubmission: map known callback JSON fields to typed sub-message.
- ChannelConversationTurnRunner.ToInboundMessage + InboundMessage.Extra: workflow/LLM handoff via typed access; literal-key path becomes deprecated inbound fallback only.
- ChannelCardActionRouting: consume typed WorkflowResumeActionPayload; literal-key rebuild path explicitly deprecated.
- TextUserLlmOptionsRenderer: route LLM selection through typed payload.
- Tests: Lark card round-trip (typed payload → relay → workflow resume); negative tests for missing/malformed typed payload.

Closes #1004

⟦AI:AUTO-LOOP⟧

* fix(pr1005/r2): add Refactor (iter93/cluster-093) self-doc + typed apply_preset + adapter callback emission tests

- chat_activity.proto + ChannelCardActionRouting.TryBuildWorkflowResumeCommand + NyxIdRelayTransport.MapKnownPayloads + Lark/Telegram composer projection: add Refactor (iter93/cluster-093) Old/New self-doc blocks; old = workflow resume + LLM selection control in open arguments map; new = typed payload in repo-owned contract, arguments only for adapter/3p extension + inbound callback JSON compat.
- ChannelConversationTurnRunnerTests: typed apply_preset behavior test (LlmSelection.PresetId → ApplyPresetAsync called with PresetId).
- NyxIdRelayTransportTests: lp / llm_apply_preset action id maps to typed apply_preset with PresetId filled.
- TelegramMessageComposerTests: ActionElement.LlmSelection projects into callback JSON v with llm_action/service_id/preset_id keys.

⟦AI:AUTO-LOOP⟧
#1008)

* iter94 cluster-809: migrate OpenAI Realtime session.update to GA shape (#809)

Phase 9 r1 consensus openai-realtime-session-update-ga-shape-migration:
- IOpenAIRealtimeSession + OpenAIRealtimeSession + OpenAIRealtimeProvider: map session.update payload to GA shape per OpenAI docs (audio.input / audio.output / instructions / voice / tools fields organized per GA contract).
- Tests: cover GA shape serialization + provider integration paths.
- Unblocks ADR-0026 Stage 5.

Closes #809

⟦AI:AUTO-LOOP⟧

* fix(pr1008/r2): add Refactor (iter94/cluster-809) self-doc + simplify test stub

- OpenAIRealtimeProvider.cs + OpenAIRealtimeSession.SendSessionUpdateAsync: add Refactor (iter94/cluster-809) Old/New self-doc; old = OpenAI SDK typed beta session options; new = GA shape session.update JSON envelope per OpenAI docs.
- OpenAIRealtimeProviderTests.cs: replace _unreachableEvent + #pragma CS0162 with direct async iterator helper for cleaner test stub.

⟦AI:AUTO-LOOP⟧
… with honest watermarks (#1009)

* iter94 cluster-094b: rename workflow capabilities to startup artifact with honest watermarks (#1007)

Phase 9 r1 consensus rename-workflow-capabilities-as-startup-artifact-with-honest-watermarks:
- WorkflowCapabilitiesCurrentStateDocumentMetadataProvider → WorkflowCapabilitiesStartupArtifactMetadataProvider; expose generated_at + schema_version watermarks instead of fake AuthorityStateVersion = 1.
- workflow_projection_transport.proto: declare startup artifact contract with honest fields; remove StateVersion = 1 / LastEventId = "startup-materialization" fake authority fields.
- WorkflowCapabilitiesStartupMaterializer: produce startup artifact, not CurrentStateDocument.
- WorkflowCatalogReadModelQueryPort + WorkflowCatalogReadModelMapper: consume new startup artifact shape.
- DI registration + integration tests updated to reflect renamed artifact.

Closes #1007

⟦AI:AUTO-LOOP⟧

* fix(pr1009/r2): add Refactor (iter94/cluster-094b) Old/New self-doc on startup artifact surface

- WorkflowCapabilitiesStartupArtifactMetadataProvider + WorkflowCapabilitiesStartupMaterializer + WorkflowCatalogReadModelMapper: add Old/New self-doc; old = workflow capabilities was a current-state document with fake StateVersion = 1 / LastEventId = "startup-materialization"; new = workflow capabilities is a startup artifact with honest GeneratedAtUtc / SchemaVersion watermarks, no fake authoritative version.

⟦AI:AUTO-LOOP⟧
…late/PR 吞吐崩/手动 pause 标记)

per Auric 2026-05-25 "设置一下停止条件或者降速条件吧"。loop 之前无限跑直到 ctrl-c,缺自动 gate。本次加 5 stop + 5 throttle 条件,resume 只走 maintainer 显式信号(删标记或 /loop)。

⟦AI:AUTO-LOOP⟧
…ority (#1010)

* iter94 cluster-094a: split UserAgentCatalogDocument readmodel by authority (#1006)

Phase 9 r2 consensus mechanical-split-user-agent-catalog-readmodel-by-authority:
- Drop UserAgentCatalogDocument synthetic StateVersion = catalogVersion + runnerVersion.
- catalog readmodel only ← catalogVersion (authoritative): UserAgentCatalogReadModelEntry + UserAgentCatalogProjector + UserAgentCatalogQueryPort.
- execution readmodel split out: SkillRunnerExecutionDocument.Partial + SkillRunnerExecutionProjector + SkillRunnerExecutionDocumentMetadataProvider ← runnerVersion (authoritative).
- DI registration adds new projection + retired-actor spec entry.
- proto: skill_runner.proto + user_agent_catalog.proto updated for split contract.
- docs/canon/daily-command-pipeline.md: document the split readmodel topology.
- Consumer-side join handled at query callers; no aggregate actor introduced.

Closes #1006

⟦AI:AUTO-LOOP⟧

* fix(pr1010/r2): move execution join from query port to API endpoint layer

- Remove UserAgentCatalogQueryPort in-port execution readmodel join; query port now only exposes catalog authority readmodel.
- New SkillRunnerExecutionQueryPort + ISkillRunnerExecutionQueryPort exposes execution authority readmodel.
- AgentBuilderTool (API endpoint surface) now calls both query ports and performs consumer-side join inside the endpoint layer (CLAUDE.md "聚合必须 actor 化" — query port-layer join was the violation; API endpoint-layer join is consumer-side compliant).
- Cluster Refactor (iter94/cluster-094a) Old/New self-doc added to UserAgentCatalogProjector + SkillRunnerExecutionProjector.
- Per-id endpoint-level join test added to AgentBuilderToolTests; UnifyCallerScopeAcceptanceTests updated to reflect query port narrowing.

⟦AI:AUTO-LOOP⟧

* fix(pr1010/r3): drop execution Status from lifecycle command admission; runner owns Enabled/Disabled

Per reflector r2 META_RESOLVED:retry-fix (semantic 降级, no aggregate actor):
- AgentBuilderTool.RequireManagedAgentAsync / RunAgentAsync / DisableAgentAsync / EnableAgentAsync: only use catalog authority (caller visible + agent exists + agent type supports managed lifecycle). No more runner execution Status pre-check; run_agent sync return only promises accepted-for-dispatch.
- SkillRunnerGAgent.TriggerAsync: in own turn decides Enabled/Disabled and publishes accept/reject state event; client observes via readmodel.
- /agents + /agent-status: keep presentation join, but doc + canon mark it presentation-only — must not become internal admission fact source or aggregate query contract.
- architecture_guards.sh: guard prevents new lifecycle command paths from reading execution Status.

⟦AI:AUTO-LOOP⟧

* fix(pr1010/r4): add SkillRunnerExecutionQueryPort unit tests

Cover GetAsync / QueryByAgentIdsAsync branches: trim, blank filter, empty id filter, dedupe, Eq vs In filter selection, returned document empty-id filter, same id max-StateVersion pick.

⟦AI:AUTO-LOOP⟧
…; split admission vs committed/handled/observed ACK (#1012)

* iter95 cluster-095: define runtime-neutral DispatchAdmission contract; split admission vs committed/handled/observed ACK (#1011)

Phase 9 r1 consensus define-dispatch-admission-contract-split-from-committed-observed-acks:
- IActorDispatchPort.DispatchAsync now returns runtime-neutral DispatchAdmission (accepted + commandId + ackedAt); all runtimes (Local + Orleans + projection scope) return at admission stage and never wait through actor handler execution.
- committed / handled / read-model observed ACKs split into independent observation contracts (DispatchAdmissionFollowUp event + readmodel watermark).
- CommandDispatchPipeline + ActorCommandTargetDispatcher + DefaultCommandOutcomeDispatchService + DefaultCommandInteractionService updated to consume new admission shape and route follow-up observations separately.
- ProjectionScopeActorRuntime: projection scope dispatch honors admission contract.
- Tests across CQRS Core + projection + AI + foundation runtime updated to assert admission ACK semantics (no longer assume sync return == committed).

Closes #1011

⟦AI:AUTO-LOOP⟧

* fix(pr1012/r2): demote DispatchAdmissionFollowUp surface to internal-pending + add DispatchAdmissionFactory unit tests

- IActorDispatchPort.cs: DispatchAdmissionFollowUpStage / DispatchAdmissionFollowUp moved to internal API with explicit "reserved for iter96+ observation pipeline" remarks (no public abstraction with 0 callers).
- DispatchAdmissionFactoryTests.cs: covers blank/missing envelope.Id stable command id generation, blank/missing Propagation.CorrelationId fallback to command id, trim preservation, null envelope / illegal actor throws.

⟦AI:AUTO-LOOP⟧
…#989)

* iter89 cluster-089: Elasticsearch projection store 用 monotonic clock 计 elapsedMs

cluster-089-projection-provider-elapsed-clock(audit-iter-89,rules RULE-TIMING-MONOTONIC-ELAPSED + RULE-TIMING-CLOCK-INJECTION):
- Old: ElasticsearchOptimisticWriter + Store 用 DateTimeOffset.UtcNow 减法计 elapsedMs(wall-clock,非 monotonic;NTP adjust 时可能负值或跳跃)
- New: 用 Stopwatch.GetTimestamp / Environment.TickCount64 monotonic;wall-clock 仅用于 semantic timestamps

变更(2 文件 +22/-14):
- ElasticsearchOptimisticWriter.cs:48/187/199/211 改 monotonic
- ElasticsearchProjectionDocumentStore.cs:127/147/159 同

验证:Elasticsearch projection 编译 + 测试 pass + 两个 ci/*_guards.sh pass。

⟦AI:AUTO-LOOP⟧

* fix(pr989/r2): add source-regression test locking monotonic clock for elapsed logging

ElasticsearchProjectionElapsedLoggingSourceRegressionTests reads ElasticsearchOptimisticWriter.cs and ElasticsearchProjectionDocumentStore.cs source content, asserts:
- no DateTimeOffset.UtcNow subtraction (no wall-clock elapsed)
- presence of Stopwatch.GetTimestamp() + Stopwatch.GetElapsedTime() (monotonic clock)

Prevents accidental regression to wall-clock subtraction in projection store elapsed logging.

⟦AI:AUTO-LOOP⟧
Session 漏 Phase 5 remote CI watch,11 个 cluster PR squash merge 后 trunk auto-refact-dev head a344bee 出 5 个 test 挂。逐个修:

projection-provider-e2e (Aevatar.Integration.Tests.ScriptReadModelProductionProvidersIntegrationTests):
- 3 个 ES + Neo4j persistence test 挂,根因 #1010 (cluster-094a UserAgentCatalog readmodel split) + #1012 (cluster-095 DispatchAdmission) 影响 ProjectionScopeActorRuntime + ProjectionScopeActivationService 的 admission contract;projection scope DI registration 没全跟上。补 ProjectionScopeObservationRelayBinding + 调整 EventSink/Materialization/ProjectionScopeStatus 3 个 runtime registration 以适配新 admission。

coverage-quality:
- StreamingProxyCommittedStateProjectionActivationPlanProviderTests.CommittedRoomParticipantStateEvent_ShouldActivateProjectionAndPopulateReadModelSnapshot:#1002 sunset header / guard 影响 projection activation 走 base runtime,通过 ProjectionScopeGAgentBase 简化 + ProjectionScopeActivationService 走新 binding 修。
- NyxIdChatEndpointsCoverageTests.HandleRelayWebhookAsync_ShouldDispatchCardAction_ToConversationActor_ForWorkflowResumeSubmit:#1005 typed payload migration 后 NyxIdRelayTransport 还残留 17 行旧 schema fallback code,删除让 webhook 走 typed payload 单一路径。

8 files changed, +30/-35 LOC. 5 test passes locally.

⟦AI:AUTO-LOOP⟧
Direct push to auto-refact-dev (SKILL Phase 4 hotfix path) — 5 test 全修:
- StreamingProxyCommittedStateProjectionActivationPlanProvider...CommittedRoomParticipantStateEvent_...
- NyxIdChatEndpointsCoverageTests.HandleRelayWebhookAsync_ShouldDispatchCardAction_...
- ScriptReadModelProductionProvidersIntegrationTests x3 (ES + Neo4j persistence)

⟦AI:AUTO-LOOP⟧
per Auric 2026-05-25 "改一下分支保护,必须ci 绿了才能合并"。
本 session 11 PR 全 reviewer-3/3 approve 后 squash merge 没等 CI →
trunk 5 test 挂(hotfix ef7962d 修)。auto-refact-dev branch protection 已
设(8 required checks + strict + enforce_admins=true);controller 行为
红线同步进 SKILL hard rules 防漏。

⟦AI:AUTO-LOOP⟧
# Conflicts:
#	test/Aevatar.GAgentService.Integration.Tests/ScopeServiceEndpointsStreamTests.cs
…re-loop

# Conflicts:
#	agents/Aevatar.GAgents.StatusDashboard/Executors/HttpStatusProbeExecutor.cs
#	src/Aevatar.ChatRouting.Abstractions/chat_route_policy.proto
#	src/Aevatar.Mainnet.Host.Api/Responses/ResponsesEndpoints.cs
#	src/platform/Aevatar.GAgentService.Application/Responses/MessagesCommandFacade.cs
#	src/platform/Aevatar.GAgentService.Application/Responses/ResponsesCommandFacade.cs
#	src/platform/Aevatar.GAgentService.Application/Responses/ResponsesForwardingApplicationService.cs
#	test/Aevatar.GAgentService.Tests/Application/MessagesCommandFacadeTests.cs
#	test/Aevatar.GAgentService.Tests/Application/ResponsesCommandFacadeTests.cs
#	test/Aevatar.GAgents.StatusDashboard.Tests/HttpStatusProbeExecutorTests.cs
#	test/Aevatar.Hosting.Tests/MainnetResponsesEndpointsTests.cs
After populating WorkflowResume / LlmSelection typed sub-messages,
remove the matching keys from the open-ended Arguments map so each
piece of data has a single semantic location (typed field), not two.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
IActorDispatchPort.DispatchAsync returns Task<DispatchAdmission>; the
prior Task.CompletedTask stubs no longer satisfy that signature. Add
ActorDispatchPortTestSupport.AcceptAsync that builds an admission via
DispatchAdmissionFactory from the captured actorId + envelope, and
switch every Substitute setup to it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Production now emits only TextMessageEnd + RunFinished for the pong
path (no Start/Content frames), and surfaces the upstream LLM message
verbatim instead of wrapping it with "LLM request failed [tools=none]:".
The assertions were stale; this updates them to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reading state immediately after EnsureRuntime/Run was racing with the
async binding event and read-model materialization. Add two helpers on
ScriptEvolutionIntegrationTestKit -- WaitForScriptBindingAsync (polls
the event store for the matching ScriptBehaviorBoundEvent) and
WaitForStateAsync<TState> (polls the actor's StateRoot until a
predicate matches) -- and adopt them across HybridServiceUpgrade,
ScriptDefinitionRuntimeContract, and ScriptGAgentFactoryLifecycle
tests so assertions observe the committed state instead of a snapshot
in flight.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mainnet sets EnableSshExecTool=true but never registered an
IToolApprovalHandler, so NyxIdAgentToolSource.DiscoverToolsAsync threw
on every Lark conversation turn and the bot stopped replying. The
canonical yielding handler keeps tool exposure gated while approval
flows yield into RoleGAgent's actor-owned remote continuation via
NyxIdRemoteToolApprovalPort. iter23/cluster-001-nyxid-tool-approval-polling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@eanzhao eanzhao merged commit 0688384 into dev May 26, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants