feat(recovery,context): tier-2 intent-holder arbiter + remaining-delta pinning + surfaced signal events
- recovery: two-tier repair ladder — owner then arbiter (recovery stage recast
as intent-holder, holds initial intent, reconciles cross-file contract disputes)
with independent INTENT_ROUTE_BUDGET; RecoveryRoutingTest coverage
- context: remaining-delta checklist pinning (RemainingDelta{Pinning,Entry}Test)
- surface write-only events (session name, quality signals) into stats/browse
- parseToolArguments lenient-Json fallback for bare-key drift
- tools: ChildProcess seam
This commit is contained in:
@@ -16,3 +16,18 @@ data class InitialIntentEvent(
|
||||
val sessionId: SessionId,
|
||||
val intent: String,
|
||||
) : EventPayload
|
||||
|
||||
/**
|
||||
* A short, human-readable title for the session (e.g. "Add auth to account service"), derived
|
||||
* once from the initial intent by the Talkie model. Recorded as an event (invariant #9) so the
|
||||
* nondeterministic name is generated a single time and replays identically — the TUI shows this
|
||||
* instead of the opaque workflow id. Non-authoritative display metadata; nothing in the core
|
||||
* decision path reads it.
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("SessionNamed")
|
||||
data class SessionNamedEvent(
|
||||
val sessionId: SessionId,
|
||||
val name: String,
|
||||
val timestampMs: Long,
|
||||
) : EventPayload
|
||||
|
||||
@@ -36,6 +36,23 @@ data class WorkflowFailedEvent(
|
||||
val retryExhausted: Boolean,
|
||||
) : EventPayload
|
||||
|
||||
/**
|
||||
* Records that the operator approved access to a specific path OUTSIDE the workspace root
|
||||
* (a `file_read`/`list_dir` target). The intent plane raises PROMPT_USER for any out-of-workspace
|
||||
* read; on approval this event is recorded so (a) subsequent reads of the same path this session
|
||||
* skip the prompt, and (b) the tool jail is widened to admit it. [path] is the lexically-resolved
|
||||
* absolute path — the recorded fact replay reads instead of re-prompting (invariants #8/#9).
|
||||
* Scope is the session: nothing here persists across sessions. Privileged locations are BLOCKED
|
||||
* upstream and never reach this event.
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("OutsidePathAccessGranted")
|
||||
data class OutsidePathAccessGrantedEvent(
|
||||
val sessionId: SessionId,
|
||||
val stageId: StageId,
|
||||
val path: String,
|
||||
) : EventPayload
|
||||
|
||||
@Serializable
|
||||
@SerialName("OrchestrationPaused")
|
||||
data class OrchestrationPausedEvent(
|
||||
@@ -115,6 +132,12 @@ data class FailureTicketOpenedEvent(
|
||||
// Fingerprint of [evidence] (whitespace/digit-normalised, see FailureFingerprint) at route time.
|
||||
// The next route compares against this to decide progress vs. no-progress. Empty on old events.
|
||||
val fingerprint: String = "",
|
||||
// Tier-2 escalation marker. False = tier-1, the ticket routed to the file's own author to patch its
|
||||
// own layer. True = the author loop exhausted its budget, so the ticket escalated to the arbiter
|
||||
// (the recovery stage recast as intent-holder): the failure is a cross-file CONTRACT dispute no
|
||||
// single owner can settle, so the arbiter is given the initial intent as authority and told to
|
||||
// reconcile ALL the named files in one pass. Defaulted for backward-compatible replay.
|
||||
val escalated: Boolean = false,
|
||||
) : EventPayload
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,5 +13,9 @@ data class ToolRequest(
|
||||
val stageId: StageId,
|
||||
val toolName: String,
|
||||
@Serializable(with = AnyMapSerializer::class)
|
||||
val parameters: Map<String, Any> = emptyMap()
|
||||
val parameters: Map<String, Any> = emptyMap(),
|
||||
// Paths outside the workspace root that the operator has approved for this call (recorded via
|
||||
// OutsidePathAccessGrantedEvent). Read tools widen their containment jail to admit these; write
|
||||
// tools ignore them, so writes stay workspace-jailed. Empty by default = pure workspace jail.
|
||||
val grantedPaths: Set<String> = emptySet(),
|
||||
)
|
||||
|
||||
@@ -49,6 +49,7 @@ import com.correx.core.events.events.L3MemoryRetrievedEvent
|
||||
import com.correx.core.events.events.LowQualityExtractionEvent
|
||||
import com.correx.core.events.events.InferenceCompletedEvent
|
||||
import com.correx.core.events.events.InitialIntentEvent
|
||||
import com.correx.core.events.events.SessionNamedEvent
|
||||
import com.correx.core.events.events.InferenceFailedEvent
|
||||
import com.correx.core.events.events.InferenceStartedEvent
|
||||
import com.correx.core.events.events.InferenceTimeoutEvent
|
||||
@@ -63,6 +64,7 @@ import com.correx.core.events.events.RepoMapComputedEvent
|
||||
import com.correx.core.events.events.RetryAttemptedEvent
|
||||
import com.correx.core.events.events.RetrySalvageDecidedEvent
|
||||
import com.correx.core.events.events.FailureTicketOpenedEvent
|
||||
import com.correx.core.events.events.OutsidePathAccessGrantedEvent
|
||||
import com.correx.core.events.events.WorkspaceStateObservedEvent
|
||||
import com.correx.core.events.events.RiskAssessedEvent
|
||||
import com.correx.core.events.events.SourceFetchedEvent
|
||||
@@ -123,6 +125,7 @@ val eventModule = SerializersModule {
|
||||
subclass(PreemptRedirectEvent::class)
|
||||
subclass(PreemptRedirectBlockedEvent::class)
|
||||
subclass(InitialIntentEvent::class)
|
||||
subclass(SessionNamedEvent::class)
|
||||
subclass(StageFailedEvent::class)
|
||||
subclass(StageCompletedEvent::class)
|
||||
subclass(TransitionExecutedEvent::class)
|
||||
@@ -151,6 +154,7 @@ val eventModule = SerializersModule {
|
||||
subclass(RetryAttemptedEvent::class)
|
||||
subclass(RetrySalvageDecidedEvent::class)
|
||||
subclass(FailureTicketOpenedEvent::class)
|
||||
subclass(OutsidePathAccessGrantedEvent::class)
|
||||
subclass(RefinementIterationEvent::class)
|
||||
subclass(RepoMapComputedEvent::class)
|
||||
subclass(WorkspaceStateObservedEvent::class)
|
||||
|
||||
Reference in New Issue
Block a user