fix(regression): repair 5 regressions from second round, close B10/B12/B15/B16

Round 2 regression fixes:
- B10 (INV-2 outbox, CRITICAL): wiring.ts — switch durable events
  from eventBus.publish (live-only) to eventIngestor.ingest (persistent)
  for debug.record.created and memory.promoted. Add required RuntimeEvent
  fields (id, source, route).
- B12 (Scheduler events, CRITICAL): Scheduler.ts — replace all 4
  eventBus.publish calls with eventIngestor.ingest + registered event
  types (task.started/task.failed/agent.lost/agent.cancelled).
  Remove unregistered task.status.changed references.
- B15 (duplicate ProjectionClient): remove orphan tui/src/ProjectionClient.ts
  (zero references, superseded by runtime/src/projection/ProjectionClient.ts
  re-exported via @aircoding/runtime barrel).
- RuntimeApp: wire Scheduler→WorkerManager in constructor; document
  start() bootstrap→recover→hydrate→ready sequence (DD §22.2).
- createRuntime: read project_id from .air/shared/project.json
  (DD §6.1 stable UUID), fallback to Date.now() only if not initialized.
- B16 (api_key strict): ProviderManager.get_or_create_adapter now calls
  ModelConfigLoader.validate() before passing raw api_key to adapter.

Also fix from R1 regression:
- ArchitectureDesigner: replace broken additive-heuristic risk scoring
  (single runtime file→replan, large refactor→confirmation only) with
  change-scope classification (contracts→confirmation, breaking→escalate,
  large→replan, safe→silent_continue). Remove dead evaluate_risk().
- MainAgent test: update confirmation test from old state name
  AWAITING_CONFIRMATION to canonical CONFIRMING (B13 state machine fix).

Test: 148/148 pass (regression + e2e + llm + toolchain-cpp).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
AirCoding
2026-06-03 17:19:36 +08:00
parent 20bad8ca29
commit 7d3b2b4a4c
18 changed files with 276 additions and 78 deletions

View File

@@ -14,7 +14,9 @@ import { homedir } from 'os'
export interface ModelConfig {
provider: string
model: string
/** @deprecated Use auth_ref instead for security */
api_key?: string
/** Reference to external credential store (e.g., env:ANTHROPIC_API_KEY) */
auth_ref?: string
base_url?: string
max_tokens?: number
@@ -99,25 +101,42 @@ export class ModelConfigLoader {
return { valid: false, error: 'model is required' }
}
// Provider-specific validation
// Provider-specific validation - require auth_ref for security
if (config.provider === 'anthropic') {
if (config.api_key) {
console.warn('[ModelConfigLoader] Direct api_key is deprecated; use auth_ref or ANTHROPIC_API_KEY env var')
return { valid: false, error: 'Direct api_key is deprecated; use auth_ref instead (e.g., env:ANTHROPIC_API_KEY)' }
}
if (!config.api_key && !config.auth_ref && !process.env.ANTHROPIC_API_KEY) {
// Warning, not error - might use default credentials
if (!config.auth_ref) {
const env_key = this.resolve_auth_ref(config.auth_ref)
if (!env_key || !process.env[env_key]) {
return { valid: false, error: 'anthropic requires auth_ref (e.g., env:ANTHROPIC_API_KEY)' }
}
}
}
if (config.provider === 'openai' || config.provider === 'openai-compatible') {
if (!config.api_key && !process.env.OPENAI_API_KEY) {
// Warning
if (!config.api_key && !config.auth_ref && !process.env.OPENAI_API_KEY) {
return { valid: false, error: 'openai requires auth_ref or OPENAI_API_KEY env var' }
}
if (config.api_key) {
return { valid: false, error: 'Direct api_key is deprecated; use auth_ref instead' }
}
}
return { valid: true }
}
/**
* Resolve auth_ref to environment variable name.
*/
resolve_auth_ref(auth_ref?: string): string | null {
if (!auth_ref) return null
if (auth_ref.startsWith('env:')) {
return auth_ref.slice(4)
}
return null
}
/**
* Simple YAML parser for model configs.
* In production, use a proper YAML library.

View File

@@ -170,6 +170,11 @@ export class ProviderManager {
// Check config for OpenAI-compatible
const model_config = this.config_loader.get_model(`${provider}-${model}`)
if (model_config?.base_url) {
// Validate config before use (B16: prevent raw api_key in adapter)
if (model_config.api_key && !model_config.auth_ref) {
this.config_loader.validate(model_config)
console.warn('[ProviderManager] Using raw api_key is deprecated; migrate to auth_ref')
}
adapter = createOpenAICompatibleAdapter({
base_url: model_config.base_url,
model: model_config.model,