feat(ingress): ingress layer v1 — chat routing config actor + resolver + 4 text entries + /ws/voice (#691–#696)#699
Conversation
…— last round Applied 4 fixes (FIX_DONE:687:round-3:applied-4:rejected-0:blocked-0): - (A) architect: add missing Refactor (iter15/cluster-024) anchor on AIGAgentBase.cs:202 documenting the deleted protected ChatAsync helper surface (subclasses now use ChatStreamAsync + local aggregation only when needed) - (A) architect: remove inaccurate Refactor comment from ScriptGenerateGAgent.GenerateWithReasoningAsync — that method was already streaming on origin/dev. Only GenerateAsync keeps the direct-ChatAsync refactor comment - (A) architect: same removal on WorkflowGenerateGAgent.GenerateWithReasoningAsync - (A) tests: widen ChatRuntimeStreamingBufferTests source-regression scan from src/Aevatar.AI.Core only to: src/Aevatar.AI.Core + src/Aevatar.Studio.Hosting + agents/Aevatar.GAgents.ChatbotClassifier, ignoring provider-boundary abstractions and comment-only lines Build pass; Aevatar.AI.Tests 558 pass; Aevatar.Studio.Tests 521 pass; test_stability_guards pass; git diff --check pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Foundation for the unified ingress layer (#672 text router + #674 voice router). This commit only introduces the wire shape and the architecture decision — no behavior changes yet; nothing else in the repo depends on the new project. New artifacts: - proto/aevatar/chat_routing/v1/chat_route_policy.proto (single proto in src/Aevatar.ChatRouting.Abstractions/) with State / Rule / Match / Action / Decision / Input / VoiceInput / VoiceTargetRef messages and ChatSourceKind / ToolMode / VoiceCodec / VoiceConversationMode / VadMode enums. - ADR-0024: chat-route-policy form (config actor + boundary resolver + readmodel), endpoint naming (/ws/voice not /ws/chat), v1 action subset (ForwardToGAgent + ForwardToModel), and boundaries with #568 / #596 / #608 / #560. Project layout follows Aevatar.Foundation.Abstractions: proto-only csproj with Google.Protobuf + Grpc.Tools. ChatRouteCallerScope mirrors the existing OwnerScope field-for-field to keep this project at the bottom of the dependency graph; cross-project consolidation is deferred. Closes part of #691. Tracks toward milestone "Ingress layer v1" (#20). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3d232cd4a9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Wipes rules and clears default_target. Caller responsibility to set | ||
| // a sane default_target after — resolver will fall back to env default | ||
| // until a valid one is upserted. |
There was a problem hiding this comment.
Keep reset from clearing required default target
When an existing policy actor receives ResetChatRoutePolicyRequested, this contract says it clears default_target, but the same proto/ADR defines default_target as required whenever the actor exists and the fallback path only applies when the actor/readmodel is unavailable. In the reset scenario the readmodel would still return a policy snapshot, just with no routable default action, so ingress has no valid route until a separate upsert happens; make reset preserve or atomically replace the default target instead of producing an invalid persisted policy.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed in a297c1d. ResetChatRoutePolicyRequested removed entirely — default_target is REQUIRED whenever the actor exists, and the env fallback (ADR D6) only covers actor-absent / readmodel-unavailable cold start. Callers that want to wipe and replace should use UpsertChatRoutePolicyRequested with the new default_target and an empty rules list — atomic, single committed event, no transient invalid window.
Triple-codex design solver with fixed roles (mirror Phase 8's architect/tests/quality pattern): - solver-minimal: smallest viable change; documented exception OK if scope narrow - solver-structural: CLAUDE-aligned, new abstraction allowed if justified - solver-delete: question necessity; abstain if feature genuinely needed 4th codex meta-judge arbitrates: - 3/3 unanimous → auto-dispatch implement (skip maintainer decision) - Split with named technical divergence → converge (max 2 rounds) - Architecture philosophy triggers → escalate to human (always) Auric's policy: 3/3 unanimous required. "早暴露问题比晚暴露问题好" — 2/3 majority would let split designs slip past while leaving harder problems for later. Hardcoded escalation triggers (always go to human): - Top-level CLAUDE.md clause change - New core abstraction (actor type / envelope kind / pipeline phase) - docs/canon/* repo vocabulary change - Rule exception broader than this one transient context - Cross-cluster coupling - Performance constraint unverifiable locally - human_brief.why_needs_design contains philosophy keywords GitHub traceability per Auric: every dispatch/judgment/round-result posts bilingual comment to the issue; new labels phase9-solving / phase9-judging / phase9-converging / phase9-auto-solve. Default OFF per cluster — operator opts in via auto_solve flag or phase9-auto-solve label. Maintainer comment mid-Phase-9 halts the loop and returns to Phase 7 dialog (human input always wins). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cluster-024: ChatStreamAsync as the only AI executor
| ForwardToGAgent forward_to_gagent = 2; | ||
| ForwardToWorkflow forward_to_workflow = 3; | ||
| Reject reject = 4; | ||
| Bypass bypass = 5; |
There was a problem hiding this comment.
[codex] severity=major, category=arch
Bypass is part of ChatRouteAction, and the same type is used by ChatRoutePolicyState.default_target and ChatRouteRule.action, so a persisted policy can encode the dev-only /ws/voice/{actorId} bypass path. That contradicts the ADR text (D5: "the policy-aware endpoint should never produce it") and the field's own comment ("not a rule action"), and it weakens CLAUDE.md's "API 字段单一语义" by making one action type serve both policy decisions and the explicit-actor bypass contract. Consider splitting the types — e.g. keep persisted policy actions to ForwardToModel | ForwardToGAgent | Reject, and give the dev/explicit-entry path a separate action message if Bypass still needs a wire shape.
There was a problem hiding this comment.
Fixed in a297c1d. Bypass removed from ChatRouteAction (deleted message Bypass; tag 5 + name bypass are reserved on ChatRouteAction to prevent silent reuse). The dev /ws/voice/{actorId} endpoint never produces a ChatRouteAction — it reads actorId from the route and short-circuits the resolver — so no replacement wire shape is needed. ADR-0024 §D5 rewritten to spell this out.
| string rule_id = 1; | ||
| } | ||
|
|
||
| message ResetChatRoutePolicyRequested { |
There was a problem hiding this comment.
[codex] severity=major, category=design
ResetChatRoutePolicyRequested explicitly clears default_target ("Wipes rules and clears default_target"), but ChatRoutePolicyState.default_target is documented as REQUIRED when the actor exists, and ADR D6 says the env-driven fallback is only for the cold-start case where the policy actor itself doesn't exist. So a reset leaves a durable, existing policy state with no default_target that the resolver cannot distinguish from a malformed policy — the message comment's claim that "resolver will fall back to env default until a valid one is upserted" directly contradicts D6. Either make Reset require a replacement default_target, model reset as deleting/tombstoning the policy actor, or widen ADR D6 so the env fallback also covers a reset (target-less) actor — and align all three docs.
There was a problem hiding this comment.
Fixed in a297c1d — same change as your major/arch comment on Bypass. Chose option (b) from your list: dropped ResetChatRoutePolicyRequested entirely. ADR D6 stays as-is (env fallback only for actor-absent). Reset semantics now expressed as Upsert(default_target, rules=[]) — atomic, never produces an invalid persisted state. §D5 text added explaining the removal.
| <Project Path="src\Aevatar.AI.ToolProviders.Workflow\Aevatar.AI.ToolProviders.Workflow.csproj" /> | ||
| <Project Path="src\Aevatar.Bootstrap.Extensions.AI\Aevatar.Bootstrap.Extensions.AI.csproj" /> | ||
| <Project Path="src\Aevatar.Bootstrap\Aevatar.Bootstrap.csproj" /> | ||
| <Project Path="src/Aevatar.ChatRouting.Abstractions/Aevatar.ChatRouting.Abstractions.csproj" /> |
There was a problem hiding this comment.
[Consensus: 4 models] severity=minor, category=style
glm-5.1, mimo-v2.5-pro, kimi, and gemini all independently flagged this: the new <Project Path> entry uses forward slashes (src/Aevatar.ChatRouting.Abstractions/...) while its immediate neighbors — and the large majority of entries in this file — use backslashes (src\...). MSBuild accepts both, so there is no functional impact; the lone forward-slash entry wedged between two backslash entries is purely a consistency / diff-noise nit. Switch to src\Aevatar.ChatRouting.Abstractions\Aevatar.ChatRouting.Abstractions.csproj to match the surrounding block.
Per-model verbatim
- glm-5.1: Forward-slash path separator (
src/Aevatar.ChatRouting.Abstractions/...) is inconsistent with every other<Project Path>entry in this file (all use backslashes). Both work in MSBuild, but the mismatch creates unnecessary diff noise. Change tosrc\Aevatar.ChatRouting.Abstractions\Aevatar.ChatRouting.Abstractions.csprojfor consistency. - mimo-v2.5-pro: Path separator inconsistency: new entry uses forward slashes (
src/Aevatar.ChatRouting.Abstractions/...) while both immediate neighbors at lines 81 and 82 use backslashes (src\Aevatar.Bootstrap\...,src\Aevatar.Configuration\...). The slnx already has mixed separators elsewhere (lines 5–8 use/), but the inserted position between two\entries makes it visually jarring. Recommend usingsrc\Aevatar.ChatRouting.Abstractions\Aevatar.ChatRouting.Abstractions.csprojto match the surrounding block. - kimi: The new
<Project Path="src/Aevatar.ChatRouting.Abstractions/..." />uses forward slashes while every other entry in this file uses backslashes (src\...). Prefer backslashes for consistency with the existing 80+ entries. - gemini: Inconsistent path separator. Neighbors in this section (e.g., lines 79-81) use backslashes (
\), but this entry uses forward slashes (/). Suggest usingsrc\Aevatar.ChatRouting.Abstractions\Aevatar.ChatRouting.Abstractions.csprojfor consistency.
There was a problem hiding this comment.
Fixed in a297c1d. Switched <Project Path> to backslashes (src\Aevatar.ChatRouting.Abstractions\...) to match the surrounding /src/ block.
Per Auric (2026-05-19): "issues 我需要各 solver 的讨论也放回 issues, 好给人类 做判断, 修改一下." Humans need raw reasoning to override the meta-judge. Change Phase 9 GitHub traceability: - Each individual solver completion → its own issue comment with FULL output (bilingual by construction per solver-*.md prompts) - Meta-judge completion → its own issue comment with FULL judge output - 3+ comments per Phase 9 round are intentional audit trail; summary tables are forbidden This also makes manual override easy: the human sees each solver's file:line evidence + cost numbers + ESCALATE_REASON list and can add a comment "use minimal framing despite delete escalation" without needing to ssh to controller machine. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| // (CompositeCallerScopeResolver output → ChatRouteCallerScope). Moving | ||
| // OwnerScope to Foundation.Abstractions is a deferred refactor — out of | ||
| // scope for the ingress-layer milestone. | ||
| message ChatRouteCallerScope { |
There was a problem hiding this comment.
[gemini] severity=nit, category=arch
Mirroring ChatRouteCallerScope field-for-field with Aevatar.GAgents.Scheduled.OwnerScope is a sound temporary measure to avoid a circular dependency. The doc comment and ADR-0024 (§D7 + "Out of scope") already record this as a deferred refactor; consider also adding a tracking issue reference here so the duplication has a concrete consolidation owner rather than only prose. (Low priority — the ADR already documents the intent.)
5 review comments on chat_route_policy.proto, ADR-0024, and aevatar.slnx:
- (major/arch) Remove `Bypass` from `ChatRouteAction` oneof — it could be
persisted in `ChatRoutePolicyState.default_target` / `ChatRouteRule.action`,
letting a stored policy encode the dev-only `/ws/voice/{actorId}` shape.
Bypass message is deleted; tag 5 + name `bypass` are `reserved` on the
ChatRouteAction wire to prevent silent reuse. Dev endpoint reads `actorId`
from the route directly and does not need a ChatRouteAction representation.
- (major/design) Drop `ResetChatRoutePolicyRequested` — `default_target` is
REQUIRED whenever the actor exists, so a Reset that clears it would leave
an invalid persisted state (env fallback only covers actor-absent per
ADR D6). Replaced by `UpsertChatRoutePolicyRequested` with empty rules
and the desired `default_target` — atomic, single-event, no temporary
invalid window.
- (minor/style, 4-model consensus) Switch `aevatar.slnx` entry from
forward to backslash path separator to match the surrounding block.
- (nit/arch) Reference tracking issue #700 in the `ChatRouteCallerScope`
comment so the OwnerScope-consolidation deferral has a concrete owner.
- (P2) Codex independent flag of the Reset / default_target tension —
addressed by the same Reset removal above.
ADR-0024 §D5 rewritten to explain the Bypass removal and Reset drop;
§D3 oneof variant list trimmed; intro text drops `Reset*` from the command
catalogue.
Verified `dotnet build` green on the changed project; `tools/docs/lint.sh`
clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per Auric (2026-05-19): "改skills. 不要漏监控." After he opened #701 mid-session, my previous Monitor armed with hardcoded list [681,682,684] silently missed all #701 activity (4 substantive comments). Re-armed manually to include 701, but the root cause is the design pattern itself. New Mode B Monitor: - Discovers open issues every 60s tick by querying labels (refactor-design-needed OR phase9-auto-solve), never enumerated - Picks up new issues opened mid-session without re-arm - Controller-level invariant: every wakeup verifies Monitor is alive AND covers every open issue carrying the labels; gap → TaskStop + re-arm - Hard rule: hardcoding PENDING_ISSUES list is broken Added "Controller-level gap check (mandatory every wakeup)" subsection so future controller code reviews catch this regression. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
✅ Fix-check: all 5 review comments resolvedRan
5 resolved · 0 partial · 0 unresolved. Panel: deepseek-v4-pro · glm-5.1 · mimo-v2.5-pro · kimi · gemini · codex. ADR-0024 §D5 was also updated to document the 🤖 automated fix-check via /opencode-pr-fix-check |
Implements issue #692 (Ingress v1 Phase 1) on top of the #672 / ADR-0024 config-actor + boundary-resolver design. Adds the Aevatar.GAgents.ChatRouting agent package: - ChatRoutePolicyGAgent — per-scope, long-lived config aggregate over ChatRoutePolicyState. Handles UpsertChatRoutePolicyRequested and RemoveChatRouteRuleRequested, emitting committed ChatRoutePolicyUpdated events. Upsert requires a non-empty default_target (actionable error otherwise) and persists rules pre-ordered by priority, ties broken by rule_id. Config-only — no query handler, no turn dispatch (IProjectedActor). - ChatRoutePolicyCurrentStateDocument — query-side readmodel that mirrors ChatRoutePolicyState 1:1 with typed sub-messages (no Any state_root bag). chat_route_policy_readmodel.proto imports chat_route_policy.proto via Grpc.Tools AdditionalImportDirs, so the wire types stay single-sourced in Aevatar.ChatRouting.Abstractions. - ChatRoutePolicyCurrentStateProjector — overwrite-materializes committed ChatRoutePolicyState into the readmodel; state_version / last_event_id come from the committed StateEvent (no locally-invented version). - AddChatRoutingAgents registers the InMemory document projection store and is wired into the Mainnet host bootstrap. Reset command: issue #692 listed ResetChatRoutePolicyRequested, but PR #699's review removed it from chat_route_policy.proto — a reset that clears the required default_target would persist invalid state. This PR follows the current proto: two commands, no Reset. Tests (test/Aevatar.GAgents.ChatRouting.Tests, 12 green): - GAgent: upsert + remove-rule happy paths, default_target-missing rejection, rules priority ordering, unknown-rule rejection, and a guard that the actor only handles config commands. - Projector: committed-state overwrite, same-version idempotency, non-committed no-op, null-argument / null-dependency guards. - DI: AddChatRoutingAgents registers the document store reader + writer. Verification: - dotnet build aevatar.slnx — 0 errors - dotnet test test/Aevatar.GAgents.ChatRouting.Tests — 12/12 pass - tools/ci/projection_state_version_guard.sh — pass - tools/ci/projection_route_mapping_guard.sh — pass - tools/ci/architecture_guards.sh — pass (proto buf lint, projection, and studio_projection_readmodel_registration guards all green) Tracks milestone "Ingress layer v1"; part of #672. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sequential sibling to codex-refactor-loop: walks every issue in a GitHub milestone in order, with one codex per issue (implement) → stacked PR (issue N's base = issue N-1's head; issue 1 → dev) → Claude subagent reviewer → fix codex if rework → re-review until pass or hit max_review_rounds cap → advance to next issue. PRs stay open for human merge; any failure halts the train. Files: - SKILL.md (controller phase machine + dispatch rules) - REFERENCE.md (state.json schema + recovery playbook + vs refactor-loop) - prompts/implement.md (codex: implement one issue, IMPLEMENT_DONE marker) - prompts/review.md (subagent: 7-dimension PR review, REVIEW_VERDICT) - prompts/fix.md (codex: address findings, A/B/C/D/E classification) - scripts/spawn-codex.sh (copy of refactor-loop wrapper; ≥3600s floor) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…at/2026-05-19_ingress-phase1-chat-route-policy
Per Auric (2026-05-19): "凡是新回复都要完整重新让多个solver分析, 必须达成
共识才可以."
Old: MAX_CONVERGENCE_ROUNDS=2 → if not consensus, escalate.
New: no hard round cap. Loop iterates until:
- 3/3 unanimous + meta-judge consensus → auto-implement, OR
- hardcoded architecture-philosophy trigger fires → escalate, OR
- maintainer adds auto-loop-resume label with explicit framing.
Maintainer-reply-resets-the-round (mandatory):
- When auto-discover Monitor fires on a verified team-member comment
that is substantive, controller TaskStops in-flight Phase 9 codex,
treats new comment as fresh constraint, dispatches a NEW round
(not "continue convergence"; truly fresh with prior rounds as context).
- Round counter increments but does NOT trip any escalation cap.
- Previous escalation state (auto-loop-stuck label) auto-clears on reset.
Anti-spiral replacements for the dropped cap:
- Stall detection: 3 consecutive rounds with NO maintainer input AND
no solver verdict-text change → escalate as stalled:no-progress-no-input.
- Maintainer reply RESETS stall counter to 0.
- 12h cumulative solver runtime per issue (raised from 6h).
- Hardcoded architecture-philosophy triggers escalate immediately
regardless of round (no change here).
Triggered by today's #701 experience: round 2 escalated, Auric posted
substantive architecture vision in c14/c15, controller should have
auto-restarted with his comment as constraint instead of waiting for
him to pick I/II/III.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…PR revert) cluster-024 fix codex round 3 reverted this file out of PR #687 scope per quality reviewer's "unrelated drive-by" feedback. That left dev / auto-refact-dev WITHOUT the fix, and Phase 6 sync pulled the unfixed version back into auto-refact-dev. Result: when no --add-dir args passed, the `ADD_DIRS[@]` expansion under `set -u` throws "unbound variable" → spawn-codex.sh exits 1 → 3 Phase 9 solvers for #701 round 3 all failed immediately. This is a SKILL-level fix (not cluster work), so it goes on auto-refact-dev directly (not a cluster PR) and lives there. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…env fallback Adds the stateless Aevatar.ChatRouting.Core library: ChatRouteResolver (pure function), projection-backed IChatRoutePolicyQueryPort, env/options fallback provider, and AddChatRoutingCore() DI wiring in the Mainnet host. Implemented per .implement-loop/runs/implement-issue-693.md. Closes #693 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ntries Wires ChatRouteResolver into NyxIdChat create, Responses, Messages, and the relay ConversationGAgent — each resolves before its existing dispatch, with no new actor hop. Adds typed NeedsLlmReplyEvent.target_ref and the relay caller-scope field; registers src/Aevatar.ChatRouting.Abstractions as a buf module so the cross-module proto import resolves under buf lint. Implemented per .implement-loop/runs/implement-issue-694.md. Closes #694 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per Auric (2026-05-19): "codex cli, 提示词直接写到一个临时文件就可以,
输出也输出到一个临时文件, 方便debug. 改skills."
Wrapper changes:
- Add --prompt-text "..." option (wrapper auto-mktemps /tmp/codex-prompt.XXXXXXXX);
caller no longer needs to write prompt file manually
- Print "SPAWN: prompt=<path> log=<path> cd=<dir> timeout=<s>" to stderr at start
- Print "DONE: log=<path> exit=<N> prompt=<path>" to stderr at end
- Header docstring documents the file-based contract explicitly
- macOS mktemp note: trailing X pattern (BSD), no extension (codex doesn't care)
CLAUDE.md "Codex CLI 调用规范" updates:
- New "Prompt + 输出必须双 file (强制,debug 友好)" subsection
- Add inline-string and missing --log to anti-pattern list
- Auto-banner described in 标准包装 section
Debug recipe: `cat <prompt-path>` to see what codex saw;
`cat <log-path>` to see what codex did.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e audio disabled Per Phase 9 #701 consensus (Auric architecture + Option A, 3/3 unanimous round 5): - Collapse RemoteActorVoicePresenceSessionResolver from locked host-owned relay to setup/control-only bridge. Remove _gate, _state, AttachmentState, subscription handling, relay task, PCM-as-VoiceRemoteAudioInputReceived dispatch, and VoiceRemoteTransportOutput.AudioOutput forwarding. - Remote media attach now dispatches Open + immediate Close with reason "remote_audio_transport_unavailable", disposes transport, throws NotSupportedException so callers fail explicitly. - Keep remote setup/close/control envelope plumbing live through existing EventEnvelope + IActorDispatchPort (Auric pipeline: function-call boundary uses envelope, chunks never via envelope). - VoicePresenceSessionDispatch: direct host envelopes no longer accept VoiceRemoteAudioInputReceived. - VoicePresenceModule: actor still owns remote session open/close/control state; remote audio input inert; provider audio not republished as remote transport audio output. - WebSocket/WHIP endpoints surface remote_audio_transport_unavailable. - Behavior tests rewritten to assert setup/close/control remains and audio chunks never relayed through envelopes. Diff: 7 files, +172 / -440 (net -268). Build pass; VoicePresence.Tests 118/118; arch+stability guards pass. No new actor type, envelope kind, pipeline phase, or core abstraction added. Proto compatibility surface preserved (remote audio arms unused but reserved for future raw-transport restoration). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ervationLifecycle API #788(iter25 cluster-026 ScriptServiceRunCommand)和 #795(iter25 cluster-002 observation-lifecycle)单独 merge OK,但顺序 merge 后 auto-refact-dev trunk 编译挂:#788 引入的 ScriptServiceRunCommandTargetBinder 用旧 ICommandTargetBinder<,,> + CommandTargetBindingResult<>,#795 改名为 ICommandObservationLifecycle<,,,,> + CommandObservationBindingResult<>。 修复: - ScriptServiceRunInteraction.cs: ICommandTargetBinder → ICommandObservationLifecycle, BindAsync(cmd, target, ctx, ct) → BindAsync(cmd, execution, ct), CommandTargetBindingResult → CommandObservationBindingResult - ServiceCollectionExtensions.cs:DI 注册类型签名同步 - ScriptServiceRunInteractionTests.cs:测试 mock 同步 Local build:0 error。 ⟦AI:AUTO-LOOP⟧
…API mismatch 事故) ⟦AI:AUTO-LOOP⟧
…赋值,不匹配比较) ⟦AI:AUTO-LOOP⟧
刚 land 的 f0c8e1e 把 ICommandTargetBinder → ICommandObservationLifecycle 迁移, 但 BindAsync 内 target.BindRunInput 改 target.RunId 后,Execute 在 receipt 构建 时引用的 target 不是 same instance 或顺序错位 → Receipt.RunId = "" 而非 command.RunId。 修:确保 target binding 完整后再构建 receipt(或调整 BindAsync 内顺序), ScriptServiceRunInteractionTests.Interaction_ShouldAttachProjectionDispatchRuntimeAndCleanup 通过。 ⟦AI:AUTO-LOOP⟧
…tream architecture unified) (#792) * iter25 cluster-027: TelegramWaitReplyGAgent task-scoped actor (lark stream actor 统一) ⟦AI:AUTO-LOOP⟧ * fix r5 PR #792: arch guard direct State mutation → event applier + rebased over auto-refact-dev Squash of fix r1-r5 + post-#795 rebase. Direct State.Generation mutation moved to event emission + applier per actor execution model. ⟦AI:AUTO-LOOP⟧
Per-target HealthProbeTargetGAgent (configure command + self-rescheduling
tick) projects committed state events into a current-state readmodel via
the standard Projection Pipeline. Two default executors ship:
http_status (generic HTTP check with ${configuration:Key} placeholders)
and readmodel_freshness (pluggable IReadmodelFreshnessSource).
Adds /status (HTML, sub2api-style) and /api/status (JSON aggregate) on the mainnet host, both AllowAnonymous. Wires the status-dashboard agent module via AddStatusDashboard and registers a channel-bot registration freshness source. Manifest in appsettings ships six probes (self liveness/readiness, NyxID catalog, Elasticsearch, channel-bot freshness, LLM dry-run disabled until ops sets the probe token).
…uter # Conflicts: # .gitignore # agents/Aevatar.GAgents.Channel.Runtime/Aevatar.GAgents.Channel.Runtime.csproj # agents/Aevatar.GAgents.Channel.Runtime/protos/conversation_events.proto # agents/Aevatar.GAgents.NyxidChat/Aevatar.GAgents.NyxidChat.csproj # src/Aevatar.CQRS.Core/Interactions/DefaultCommandInteractionService.cs # src/Aevatar.Mainnet.Host.Api/Messages/MessagesEndpoints.cs # src/platform/Aevatar.GAgentService.Hosting/Endpoints/ScopeServiceEndpoints.cs
Summary
Lands Ingress layer v1 (milestone #20) as one PR. Closes #672, #674, and the per-phase trackers #691 #692 #693 #694 #695 #696.
Originally Phase 0 only (proto + ADR). After review feedback we collapsed the entire 6-PR train (#703 #704 #705 #709 #710) onto
feature/routerso the ingress layer ships in a single reviewable diff againstdev.What this PR contains
src/Aevatar.ChatRouting.Abstractions(chat_route_policy.proto, typed enums) and ADR-0024 — the form is "config actor + stateless boundary resolver + readmodel", explicitly not a router actor (rejected per Refactor ChatRuntime to match Harness boundary (#568) — blocks NyxID-fronted Responses API gateway #608 Harness review).agents/Aevatar.GAgents.ChatRouting:ChatRoutePolicyGAgent(per-scope config aggregate; validatesdefault_target, sorts rules by priority).ChatRoutePolicyCurrentStateDocument+ChatRoutePolicyCurrentStateProjector(overwrite-materializes committed state, version from the committedStateEvent).AddChatRoutingAgentswires materialization runtime + materializer + InMemory document store; codex caught the missing materializer registration during review and the fix is included here.src/Aevatar.ChatRouting.Core:ChatRouteResolver— pureResolve(snapshot, input), priority-ordered rules, recordsmatched_rule_id, falls through to default and env fallback. Hardened during review to skip rules with empty actions (proto3 message field can be null).IChatRoutePolicyQueryPort+ChatRoutePolicyQueryPort— readmodel-only lookup,nullsnapshot when no rows.IChatRouteFallbackProvider+ env-drivenEnvChatRouteFallbackProvider.AddChatRoutingCore()DI, wired in the Mainnet host.NyxIdChatEndpoints) —ForwardToGAgentoverrides the target;Reject→ 403. Review feedback fix: rollback only destroys the actor we created locally, never a forwarded routed target.ForwardToModelrewrites the model.ConversationGAgent— resolves inside the turn before runner admission; decision rides the typedNeedsLlmReplyEvent.target_ref;reply_tokenstays actor-private. Review feedback fix: sender NyxID is now resolved at relay ingress (INyxIdCurrentUserResolver.ResolveCurrentUserIdAsync) and carried on a new typedTransportExtras.nyx_sender_user_id, so per-user channel policies actually match./ws/voice+ dev bypass.PolicyAwareVoiceEndpoints:/ws/voiceresolves through query port + resolver with typedVoiceInput; pre-upgradeReject→ 403,ForwardToModel→ 501,ForwardToGAgent→ attach-permission + presence-session resolution. Review feedback fix: returns 503 (retryable) instead of 404 when the routed voice GAgent's module is still warming up, matching the dev bypass.MapVoicePresenceWebSocketmount on the Mainnet host:/ws/voice/{actorId}kept behind the newvoice-devauthorization policy (requiresvoice:bypassscope or admin).docs/canon/architecture-vocabulary.md, citing ADR-0024.Architecture invariants honored
ChatRoutePolicyGAgentis config-only; resolver is a library functionChatRouteDecisionis transient; not in any state protoAevatar.Foundation.VoicePresencereverse-dep grep clean/ws/voicenot/ws/chatCodex review findings — all addressed in-PR
AddChatRoutingAgentsdidn't register the materializer — readmodel never populatedactionOwnerScope.NyxUserIdcame fromregistrationScopeId— per-user channel policies always missed/ws/voicereturned 404 (not retryable) for cold voice moduleTest plan
dotnet build aevatar.slnx— greenbash tools/docs/lint.sh— green, ADR-0024 includedbash tools/ci/projection_state_version_guard.sh— greenbash tools/ci/projection_route_mapping_guard.sh— greenbash tools/ci/test_stability_guards.sh— greenAevatar.GAgents.ChatRouting.Tests(14),Aevatar.ChatRouting.Core.Tests(13),Aevatar.AI.Tests(559, includes 4 new relay regression tests),Aevatar.GAgents.Channel.Protocol.Tests(138),Aevatar.ChatRouting.Voice.Integration.Tests(9)architecture_guards.shpasses everything except the pre-existingplayground_asset_drift_guard(unrelated; zero frontend assets touched in this milestone)Related
feature/router): Ingress v1 Phase 1 — ChatRoutePolicyGAgent + Document + Projector #703 Issue #693: Ingress v1 Phase 2 — ChatRouteResolver lib + QueryPort + env fallback #704 Issue #694: Ingress v1 Phase 3 — wire ChatRouteResolver into 4 text entries #705 Issue #695: Ingress v1 Phase 4 — policy-aware /ws/voice + dev bypass /ws/voice/{actorId} #709 Issue #696: Ingress v1 Phase 5 — docs + full guards (ingress layer v1 shipped) #710