6a8a7b31c1
Three generic harness fixes from the web-ui postmortem dataset. Nothing here keys on a language, framework, build tool or task type. 1. Failure attribution. WorkflowFailedEvent carries one primary FailureAttribution (AGENT | HARNESS | WORKFLOW | ENVIRONMENT | PROVIDER | OPERATOR | UNKNOWN), defaulted to UNKNOWN so pre-field events replay unchanged. FailureAttributor is the deterministic reason->layer mapping, used both at emission and when classifying history, so the baseline and the live metric are one measurement. Emission sites set it: failWorkflow derives from the reason unless the caller knows the layer, cancellation is OPERATOR, the server catch-all falls back to HARNESS, a grounding-rejected plan is AGENT. Multi-cause chains stay on FailureTicketOpened — no second causal structure. GET /metrics/failure-attribution (FailureAttributionInspectionService, mirroring ToolReliabilityInspectionService) reports counts, share, UNKNOWN share, the preserved reasons and the ticket categories from the same sessions. Read-only: historical events are classified at READ time and reported as `inferred`, never written back over an append-only log. Baseline over the local log, 122 terminal failures: AGENT 51 (41.8%), OPERATOR 28 (23.0%), WORKFLOW 19 (15.6%), PROVIDER 15 (12.3%), HARNESS 6 (4.9%), ENVIRONMENT 3 (2.5%), UNKNOWN 0. 2. The `~` guard bug. ToolPath is now the ONE canonical normalization rule (expand a leading `~`/`~/`, keep absolutes, anchor relatives on the session working dir). Every filesystem tool, all six plane-2 path rules and the approval preview resolve through it, so policy and existence checks inspect the path the tool will operate on. `~/.gradle/init.d/offline.gradle` used to resolve to `<workspace>/~/.gradle/...`: reported non-existent AND in-workspace, so the reference gate called a real file a hallucination and the out-of-workspace prompt never fired. Containment and external-read approval behaviour are unchanged — the expanded path is simply outside the workspace, where it always belonged. 3. file_copy (#713). A first-class tool with the writer's jail, tier, receipt, replay and CAS pre/post images; static and binary assets no longer move through the model's token stream. Needed one generic split: ParamRole.SOURCE_PATH marks a path a call reads FROM, so containment gates judge both params while write-target gates (read-before-write, stale-write, write scope, write manifest) judge the mutated one. ReadBeforeWriteRule exempts any call declaring a SOURCE_PATH: its content comes from disk, not from memory, and requiring a read of a binary is unsatisfiable. Existing tools declare no SOURCE_PATH, so their behaviour is byte-identical. Tests: ToolPathTest (9), FailureAttributionTest (10), PathNormalizationRuleTest (6), FileCopyToolTest (10), plus a home-relative FileReadTool read. ./gradlew check green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4.1 KiB
4.1 KiB
core/events — AGENTS.md
Purpose
Foundational event-sourcing infrastructure: all EventPayload definitions for every domain, the serialization registry, the EventStore interface, the EventReplayer contract, and shared identity/vocabulary types used across the entire core/ layer.
Ownership
CORREX kernel team. This is the most cross-cutting module in the codebase — changes here can break every other module. Coordinate before adding or renaming event types.
Local Contracts
EventPayload— sealed interface that every domain event implements.StoredEvent/EventEnvelope/EventMetadata— envelope types wrapping payloads for storage and replay.EventStore— interface for appending and querying events. Implementations ininfrastructure/.EventReplayer<S>/DefaultEventReplayer<S>— replays a filtered event stream through aProjection<S>to rebuild state.Projection<S>— interface:initial(): S+apply(state, event): S.StateBuilder/DefaultStateBuilder— convenience builder overEventReplayer.Serialization.kt— theeventModulepolymorphic block. Every newEventPayloadsubclass must be registered here. Missing registration = silent deserialization failure.JsonEventSerializer/EventSerializer— serialize/deserializeStoredEventto JSON.EventDispatcher— broadcasts events to in-process listeners.- Domain event files:
ApprovalEvents,ArtifactEvents,ContextEvents,InferenceEvents,OrchestrationEvents,RouterEvents,SessionEvents,TaskEvents,ToolEvents,IntentEvents,RiskAssessedEvent,JournalCompactedEvent, and many more — all payload definitions live here. FailureAttribution/FailureAttributor— the terminal-failure taxonomy (AGENT,HARNESS,WORKFLOW,ENVIRONMENT,PROVIDER,OPERATOR,UNKNOWN) carried asWorkflowFailedEvent.attribution, plus the deterministic reason→layer mapping used both at emission and when classifying historical events. One primary attribution per terminal event; a multi-cause chain is the session'sFailureTicketOpenedEvents, not a second structure.LspDiagnosticsCompletedEventrecords pulled language-server diagnostics or a graceful skip reason; replay consumes this observation and never contacts the server.- Shared vocabulary:
IdentityTypes(SessionId, TaskId, etc.),Tier,TokenUsage,ToolReceipt,ToolRequest,RiskLevel,RetryPolicy,GrantScope,GrantLedger.
Work Guidance
- SILENT FAILURE TRAP: After adding any
EventPayloadsubclass, immediately add it toSerialization.kteventModuleblock. Run./gradlew checkto verify. Tests may pass without it but runtime replay will fail silently. AnyMapSerializer— custom serializer forMap<String, Any?>; use it for dynamic payloads, don't roll another.- Event classes are
@Serializable data classwith no mutable state. No methods beyond data accessors. RunBranchPushedEventrecords an optional server Git transport push only after it succeeds; its branch/base/head SHAs are observations, not values replay recalculates.RepoMapEntry.descriptoris a bounded source-purpose observation recorded with the repo map and used when constructing semantic L3 embeddings.- Do not add domain logic to events. They are records, not actors.
FailureAttributoris the one exception by design: a pure reason→layer function that must be identical for live emission and for historical classification, so it lives beside the enum it returns. WorkflowFailedEvent.attributiondefaults toUNKNOWNso pre-field events replay unchanged. Classify those at READ time (seeFailureAttributionInspectionService); never rewrite history to backfill them.EgressAllowlistProjection— special projection kept in this module because it is used by bothcore:toolintentandcore:eventsconsumers; it is a shared cross-cutting projection.
Verification
./gradlew :core:events:test --rerun-tasks
Tests in testing/contracts/ (EventsTest, EventStoreContractTest) and testing/replay/ (serialization round-trips, replay integration).
Child DOX Index
No child AGENTS.md (leaf module).