feat(validation): staged-verification funnel — contract + execution gates
Gate 2 (contract) and Gate 4 (execution) of the 4-gate staged-verification design, plus the two gate-quality fixes found in live QA. - Gate 2: KindContractTable (kind→assertions registry, universal floor, additive project overrides) + KindInference (path→kind, pure/replay-safe) + ContractAssertion model; ContractGateEvaluatedEvent (registered); ContractAssertionEvaluator seam + FileSystemContractEvaluator (FS/TEXT, COMPILER skipped); runContractGate in runPostStageGates — a stage that produces file_written artifacts must have every declared+written file exist/non-empty/parse, else retryable hand-back with per-assertion feedback. - Option A: ExecutionPlanCompiler puts concrete (non-glob) plan `writes` into StageConfig.expectedFiles, so a declared-but-unwritten file fails file_exists. - Gate 4: BuildExpectation enum (none|module|project|tests) + plan field + closed-set compiler validation; runExecutionGate resolves the alias against the bound project profile commands and runs it as a deterministic gate. - Live-QA fixes: react_entry kind so main.tsx/index.tsx aren't required to export a default component (was an unwinnable false-positive); JSONC tolerance (allowComments/allowTrailingComma) so tsconfig.json passes valid_json. Seams are null on the replay harness (no-op) — recorded events are truth (#9).
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
package com.correx.core.events.events
|
||||
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.events.types.StageId
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** The outcome of evaluating one contract assertion against a stage's produced file. */
|
||||
@Serializable
|
||||
data class ContractAssertionResult(
|
||||
val assertionId: String,
|
||||
/** Workspace-relative path the assertion was evaluated against. */
|
||||
val target: String,
|
||||
/** Contract layer the assertion came from: DERIVED / FRAMEWORK / PLANNER / HUMAN. */
|
||||
val layer: String,
|
||||
/** Evaluator that produced the verdict: FS / TEXT / COMPILER / AST. */
|
||||
val evaluator: String,
|
||||
val passed: Boolean,
|
||||
/** Concrete reason the assertion failed (or a confirmation when it passed). Kept bounded. */
|
||||
val evidence: String,
|
||||
)
|
||||
|
||||
/**
|
||||
* Environment observation (invariant #9): the Gate 2 contract assertions evaluated against a
|
||||
* stage's produced files — each with the layer it came from, the evaluator, its verdict and
|
||||
* evidence — captured at the moment the stage completed. Replay reads these recorded facts and
|
||||
* never re-inspects the filesystem.
|
||||
*
|
||||
* Gate 2 (design §1) answers "did the stage accomplish its objective?" deterministically, over the
|
||||
* files the stage declared it `produces`. A failed assertion fails the producing stage retryably,
|
||||
* with the failing assertion ids + evidence fed back verbatim — so hand-back feedback is
|
||||
* failing-test-name granular, not a vague "review said fix it".
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("ContractGateEvaluated")
|
||||
data class ContractGateEvaluatedEvent(
|
||||
val sessionId: SessionId,
|
||||
val stageId: StageId,
|
||||
val results: List<ContractAssertionResult>,
|
||||
) : EventPayload {
|
||||
val passed: Boolean get() = results.all { it.passed }
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import com.correx.core.events.events.ArtifactValidatingEvent
|
||||
import com.correx.core.events.events.BriefEchoMismatchEvent
|
||||
import com.correx.core.events.events.BriefGroundingCheckedEvent
|
||||
import com.correx.core.events.events.StaticAnalysisCompletedEvent
|
||||
import com.correx.core.events.events.ContractGateEvaluatedEvent
|
||||
import com.correx.core.events.events.PlanLintCompletedEvent
|
||||
import com.correx.core.events.events.ChatSessionStartedEvent
|
||||
import com.correx.core.events.events.SessionWorkingTaskEvent
|
||||
@@ -148,6 +149,7 @@ val eventModule = SerializersModule {
|
||||
subclass(BriefGroundingCheckedEvent::class)
|
||||
subclass(BriefEchoMismatchEvent::class)
|
||||
subclass(StaticAnalysisCompletedEvent::class)
|
||||
subclass(ContractGateEvaluatedEvent::class)
|
||||
subclass(PlanLintCompletedEvent::class)
|
||||
subclass(RiskAssessedEvent::class)
|
||||
subclass(ChatSessionStartedEvent::class)
|
||||
|
||||
Reference in New Issue
Block a user