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

@@ -25,9 +25,6 @@ export class ArchitectureDesigner {
// Analyze which components are affected
const affected = this.identify_affected_components(change.files)
// Determine result class
const risk_level = this.evaluate_risk(change, affected)
const impact: ArchitectureImpact = {
result: 'silent_continue',
affected_components: affected,
@@ -36,15 +33,27 @@ export class ArchitectureDesigner {
requires_replan: false
}
if (risk_level >= 4) {
// Classify by change scope, not mere package membership (DD §19.4):
// - contract/interface change → user confirmation (breaking → escalate)
// - broad multi-file change → replan
// - otherwise → silent_continue
const is_breaking = /deprecat|break|remove/.test(change.description.toLowerCase())
const touches_contracts = affected.includes('contracts')
const is_large = change.files.length > 10
if (touches_contracts && is_breaking) {
impact.result = 'reject_or_escalate'
impact.risks.push('High architectural risk')
} else if (risk_level >= 3) {
impact.risks.push('Breaking change to frozen contracts')
} else if (touches_contracts) {
impact.result = 'requires_user_confirmation'
impact.risks.push('Moderate impact on architecture')
} else if (risk_level >= 2) {
impact.risks.push('Contract/interface surface change')
} else if (is_large) {
impact.result = 'requires_replan'
impact.requires_replan = true
impact.risks.push('Large multi-file change requires re-planning')
} else if (is_breaking) {
impact.result = 'requires_user_confirmation'
impact.risks.push('Potentially breaking change')
}
return impact
@@ -73,13 +82,4 @@ export class ArchitectureDesigner {
}
return [...new Set(components)]
}
private evaluate_risk(change: { description: string; files: string[] }, affected: string[]): number {
let risk = 0
if (affected.includes('contracts')) risk += 3 // Contract changes are high risk
if (affected.includes('runtime')) risk += 2
if (change.files.length > 10) risk += 1
if (/deprecat|break|remove/.test(change.description.toLowerCase())) risk += 2
return risk
}
}

View File

@@ -17,12 +17,15 @@ export type MainAgentState =
| 'DELEGATING'
| 'DIRECT_MODE'
| 'SCHEDULING'
| 'AWAITING'
| 'ARCHITECTURE_DESIGNING'
| 'CONFIRMING'
| 'EXECUTING'
| 'INTERRUPTING'
| 'ARCHITECTURE_REVISING'
| 'SUMMARIZING'
| 'ERROR'
| 'TERMINATED'
export interface MainAgentConfig {
session_id: SessionID

View File

@@ -8,6 +8,7 @@
import { DebugKnowledgeStore } from '../knowledge/DebugKnowledgeStore.js'
import { LearnedMemoryStore } from '../knowledge/LearnedMemoryStore.js'
import { eventIngestor } from '../events/EventIngestor.js'
export interface KnowledgeWiring {
debug_store: DebugKnowledgeStore
@@ -61,7 +62,25 @@ export async function capture_debug_record(
updated_at: now,
metadata_json: record.metadata_json,
})
// INV-2: emit debug.record.created event AFTER external write
// INV-2: emit debug.record.created (durable) via EventIngestor AFTER external write
await eventIngestor.ingest({
id: record.id,
type: 'debug.record.created',
version: 1,
session_id: record.task_id,
project_id: '',
timestamp: now,
source: { kind: 'agent', agent_type: 'debugger' },
route: ['knowledge', 'debug'],
payload: {
debug_record_id: record.id,
task_id: record.task_id,
failure_signature: record.failure_signature,
summary: record.summary,
evidence_refs: [],
verification_refs: [],
}
})
}
/**
@@ -94,5 +113,21 @@ export async function promote_memory_entry(
updated_at: now,
metadata_json: entry.metadata_json,
})
// INV-2: emit memory.promoted event AFTER external write
// INV-2: emit memory.promoted (durable) via EventIngestor AFTER external write
await eventIngestor.ingest({
id: entry.id,
type: 'memory.promoted',
version: 1,
session_id: entry.source_entity_id || '',
project_id: '',
timestamp: now,
source: { kind: 'agent', agent_type: 'experience_miner' },
route: ['knowledge', 'memory'],
payload: {
candidate_id: entry.id,
target_ref: entry.source_entity_type || '',
promoted_by: 'experience_miner',
summary: entry.summary,
}
})
}